For FNX-22339: Recently saved bookmarks (#19835)
* Title and button for home screen recently saved bookmarks section Create bookmark item view with favicon and title * View holders and interactors for recently saved bookmarks Recent bookmark item view holder binding Create adapter for recent bookmarks. Implement controller methods. Implement view holder bindings for items Top level adapter for recent bookmarks section Retrieve list of recent bookmarks on home View holders and interactors for recently saved bookmarks Recent bookmark item view holder binding Create adapter for recent bookmarks. Implement controller methods. Implement view holder bindings for items Top level adapter for recent bookmarks section Retrieve list of recent bookmarks on home Update list on app start and when bookmarks are added View holders and interactors for recently saved bookmarks Recent bookmark item view holder binding Create adapter for recent bookmarks. Implement controller methods. Implement view holder bindings for items Top level adapter for recent bookmarks section Retrieve list of recent bookmarks on home Update list on app start and when bookmarks are added Make a use case for retrieving and updating the list of recently saved bookmarks Add adapter items and define header viewholder binding Use session interactor for header button clicks. Bind in the adapter * Retrieve list of bookmarks asynchronously on home Interactor and controller tests Address review comments Split up tests for recent bookmarks Update to new interactors Dark mode and light mode styles Refactor bookmarks home stuff * Add RecentBookmarksFeature to home Move interactor to SessionControlInteractor Clean up lint, styles, and dimens. * Bookmarks use case tests for retrieving recently saved bookmarks. Linting. * View holder tests * Match ux to designs for colors, margins, and scrolling * Clean up clean up * Tests for the view bound feature * Controller test * Clean up: check state of store in feature tests; ellipsize textviews for bookmark item; remove unused attr; format Co-authored-by: Jonathan Almeida <jalmeida@mozilla.com>upstream-sync
parent
ff9aa36885
commit
9bfe9b0787
@ -0,0 +1,44 @@
|
||||
/* 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.recentbookmarks
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import mozilla.components.support.base.feature.LifecycleAwareFeature
|
||||
import org.mozilla.fenix.components.bookmarks.BookmarksUseCase
|
||||
import org.mozilla.fenix.home.HomeFragmentAction
|
||||
import org.mozilla.fenix.home.HomeFragmentStore
|
||||
|
||||
/**
|
||||
* View-bound feature that retrieves a list of recently added [BookmarkNode]s and dispatches
|
||||
* updates to the [HomeFragmentStore].
|
||||
*
|
||||
* @param homeStore the [HomeFragmentStore]
|
||||
* @param bookmarksUseCase the [BookmarksUseCase] for retrieving the list of recently saved
|
||||
* bookmarks from storage.
|
||||
* @param scope the [CoroutineScope] used to fetch the bookmarks list
|
||||
*/
|
||||
class RecentBookmarksFeature(
|
||||
private val homeStore: HomeFragmentStore,
|
||||
private val bookmarksUseCase: BookmarksUseCase,
|
||||
private val scope: CoroutineScope
|
||||
) : LifecycleAwareFeature {
|
||||
internal var job: Job? = null
|
||||
|
||||
override fun start() {
|
||||
job = scope.launch(Dispatchers.IO) {
|
||||
val bookmarks = bookmarksUseCase.retrieveRecentBookmarks()
|
||||
|
||||
homeStore.dispatch(HomeFragmentAction.RecentBookmarksChange(bookmarks))
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
job?.cancel()
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/* 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.recentbookmarks
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import org.mozilla.fenix.home.recentbookmarks.interactor.RecentBookmarksInteractor
|
||||
import org.mozilla.fenix.home.recentbookmarks.view.RecentBookmarkItemViewHolder
|
||||
|
||||
/**
|
||||
* Adapter for binding individual bookmark items for the homescreen.
|
||||
*
|
||||
* @param interactor The [RecentBookmarksInteractor] to be passed into the view.
|
||||
*/
|
||||
class RecentBookmarksItemAdapter(
|
||||
private val interactor: RecentBookmarksInteractor
|
||||
) : ListAdapter<BookmarkNode, RecentBookmarkItemViewHolder>(RecentBookmarkItemDiffCallback) {
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): RecentBookmarkItemViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(RecentBookmarkItemViewHolder.LAYOUT_ID, parent, false)
|
||||
return RecentBookmarkItemViewHolder(view, interactor)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecentBookmarkItemViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
internal object RecentBookmarkItemDiffCallback : DiffUtil.ItemCallback<BookmarkNode>() {
|
||||
override fun areItemsTheSame(oldItem: BookmarkNode, newItem: BookmarkNode) =
|
||||
oldItem.guid == newItem.guid
|
||||
|
||||
override fun areContentsTheSame(oldItem: BookmarkNode, newItem: BookmarkNode) =
|
||||
oldItem == newItem
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/* 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.recentbookmarks.controller
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import mozilla.appservices.places.BookmarkRoot
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.nav
|
||||
import org.mozilla.fenix.home.HomeFragmentDirections
|
||||
import org.mozilla.fenix.home.recentbookmarks.interactor.RecentBookmarksInteractor
|
||||
|
||||
/**
|
||||
* An interface that handles the view manipulation of the recently saved bookmarks on the
|
||||
* Home screen.
|
||||
*/
|
||||
interface RecentBookmarksController {
|
||||
|
||||
/**
|
||||
* @see [RecentBookmarksInteractor.onRecentBookmarkClicked]
|
||||
*/
|
||||
fun handleBookmarkClicked(bookmark: BookmarkNode)
|
||||
|
||||
/**
|
||||
* @see [RecentBookmarksInteractor.onShowAllBookmarksClicked]
|
||||
*/
|
||||
fun handleShowAllBookmarksClicked()
|
||||
}
|
||||
|
||||
/**
|
||||
* The default implementation of [RecentBookmarksController].
|
||||
*/
|
||||
class DefaultRecentBookmarksController(
|
||||
private val activity: HomeActivity,
|
||||
private val navController: NavController
|
||||
) : RecentBookmarksController {
|
||||
|
||||
override fun handleBookmarkClicked(bookmark: BookmarkNode) {
|
||||
activity.openToBrowserAndLoad(
|
||||
searchTermOrURL = bookmark.url!!,
|
||||
newTab = true,
|
||||
from = BrowserDirection.FromHome
|
||||
)
|
||||
}
|
||||
|
||||
override fun handleShowAllBookmarksClicked() {
|
||||
val directions = HomeFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id)
|
||||
navController.nav(R.id.homeFragment, directions)
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/* 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.recentbookmarks.interactor
|
||||
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor
|
||||
|
||||
/**
|
||||
* Interface for recently saved bookmark related actions in the [SessionControlInteractor].
|
||||
*/
|
||||
interface RecentBookmarksInteractor {
|
||||
|
||||
/**
|
||||
* Opens the given bookmark in a new tab. Called when an user clicks on a recently saved
|
||||
* bookmark on the home screen.
|
||||
*
|
||||
* @param bookmark The bookmark that will be opened.
|
||||
*/
|
||||
fun onRecentBookmarkClicked(bookmark: BookmarkNode)
|
||||
|
||||
/**
|
||||
* Navigates to bookmark list. Called when an user clicks on the "Show all" button for
|
||||
* recently saved bookmarks on the home screen.
|
||||
*/
|
||||
fun onShowAllBookmarksClicked()
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/* 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.recentbookmarks.view
|
||||
|
||||
import android.view.View
|
||||
import kotlinx.android.synthetic.main.recent_bookmark_item.bookmark_title
|
||||
import kotlinx.android.synthetic.main.recent_bookmark_item.bookmark_subtitle
|
||||
import kotlinx.android.synthetic.main.recent_bookmark_item.bookmark_item
|
||||
import kotlinx.android.synthetic.main.recent_bookmark_item.favicon_image
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.loadIntoView
|
||||
import org.mozilla.fenix.home.recentbookmarks.interactor.RecentBookmarksInteractor
|
||||
import org.mozilla.fenix.utils.view.ViewHolder
|
||||
|
||||
class RecentBookmarkItemViewHolder(
|
||||
private val view: View,
|
||||
private val interactor: RecentBookmarksInteractor
|
||||
) : ViewHolder(view) {
|
||||
|
||||
fun bind(bookmark: BookmarkNode) {
|
||||
bookmark_title.text = bookmark.title ?: bookmark.url
|
||||
bookmark_subtitle.text = bookmark.url?.tryGetHostFromUrl() ?: bookmark.title ?: ""
|
||||
|
||||
bookmark_item.setOnClickListener {
|
||||
interactor.onRecentBookmarkClicked(bookmark)
|
||||
}
|
||||
|
||||
bookmark.url?.let {
|
||||
view.context.components.core.icons.loadIntoView(favicon_image, it)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.recent_bookmark_item
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/* 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.recentbookmarks.view
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL
|
||||
import kotlinx.android.synthetic.main.component_recent_bookmarks.view.*
|
||||
import kotlinx.android.synthetic.main.recent_bookmarks_header.*
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.home.recentbookmarks.RecentBookmarksItemAdapter
|
||||
import org.mozilla.fenix.home.recentbookmarks.interactor.RecentBookmarksInteractor
|
||||
import org.mozilla.fenix.utils.view.ViewHolder
|
||||
|
||||
class RecentBookmarksViewHolder(
|
||||
view: View,
|
||||
val interactor: RecentBookmarksInteractor
|
||||
) : ViewHolder(view) {
|
||||
|
||||
private val recentBookmarksAdapter = RecentBookmarksItemAdapter(interactor)
|
||||
|
||||
init {
|
||||
val linearLayoutManager = LinearLayoutManager(view.context, HORIZONTAL, false)
|
||||
|
||||
view.recent_bookmarks_list.apply {
|
||||
adapter = recentBookmarksAdapter
|
||||
layoutManager = linearLayoutManager
|
||||
}
|
||||
|
||||
showAllBookmarksButton.setOnClickListener {
|
||||
interactor.onShowAllBookmarksClicked()
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(bookmarks: List<BookmarkNode>) {
|
||||
recentBookmarksAdapter.submitList(bookmarks)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAYOUT_ID = R.layout.component_recent_bookmarks
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
<?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/. -->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true" android:color="@color/photonDarkGrey05" />
|
||||
<item android:state_checked="false" android:color="@color/photonLightGrey50" />
|
||||
</selector>
|
@ -1,8 +0,0 @@
|
||||
<?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/. -->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true" android:color="@color/photonDarkGrey05" />
|
||||
<item android:state_checked="false" android:color="@color/photonLightGrey05" />
|
||||
</selector>
|
@ -1,8 +0,0 @@
|
||||
<?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/. -->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true" android:color="@color/photonDarkGrey90" />
|
||||
<item android:state_checked="false" android:color="@color/photonLightGrey30" />
|
||||
</selector>
|
@ -1,39 +1,42 @@
|
||||
<?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="wrap_content" >
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="40dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/Header20TextStyle"
|
||||
android:id="@+id/recentlySavedBookmarksHeader"
|
||||
style="@style/Header20TextStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/recently_saved_bookmarks_content_description"
|
||||
android:maxLines="1"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:text="@string/recently_saved_bookmarks"
|
||||
android:layout_marginTop="@dimen/home_recently_saved_padding_top"
|
||||
android:paddingStart="@dimen/home_recently_saved_padding_start"
|
||||
android:paddingBottom="@dimen/home_recently_saved_padding_bottom"
|
||||
android:maxLines="2"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/showAllBookmarksButton"
|
||||
style="@style/Button12TextStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Button12TextStyle"
|
||||
android:contentDescription="@string/recently_saved_show_all_content_description"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/recently_saved_show_all_content_description"
|
||||
android:padding="16dp"
|
||||
android:text="@string/recently_saved_show_all"
|
||||
android:paddingStart="@dimen/home_recently_saved_padding_start"
|
||||
android:paddingEnd="@dimen/home_recently_saved_padding_end"
|
||||
android:paddingTop="@dimen/home_show_all_padding_top"
|
||||
android:paddingBottom="@dimen/home_show_all_padding_bottom"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/recentlySavedBookmarksHeader" />
|
||||
android:textColor="@color/home_show_all_button_text"
|
||||
android:maxLines="1"
|
||||
android:scrollbars="none"
|
||||
android:nestedScrollingEnabled="false"
|
||||
app:layout_constraintBottom_toBottomOf="@id/recentlySavedBookmarksHeader"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -0,0 +1,91 @@
|
||||
/* 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.recentbookmarks
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavOptions
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
||||
import mozilla.appservices.places.BookmarkRoot
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import mozilla.components.concept.storage.BookmarkNodeType
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.home.HomeFragmentDirections
|
||||
import org.mozilla.fenix.home.recentbookmarks.controller.DefaultRecentBookmarksController
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class DefaultRecentBookmarksControllerTest {
|
||||
|
||||
private val testDispatcher = TestCoroutineDispatcher()
|
||||
|
||||
@get:Rule
|
||||
val coroutinesTestRule = MainCoroutineRule(testDispatcher)
|
||||
|
||||
private val activity: HomeActivity = mockk(relaxed = true)
|
||||
private val navController: NavController = mockk(relaxUnitFun = true)
|
||||
private lateinit var controller: DefaultRecentBookmarksController
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
every { activity.openToBrowserAndLoad(any(), any(), any()) } just Runs
|
||||
every { navController.currentDestination } returns mockk {
|
||||
every { id } returns R.id.homeFragment
|
||||
}
|
||||
|
||||
controller = spyk(DefaultRecentBookmarksController(
|
||||
activity = activity,
|
||||
navController = navController
|
||||
))
|
||||
}
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
testDispatcher.cleanupTestCoroutines()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN a recently saved bookmark is clicked THEN the selected bookmark is opened`() {
|
||||
val bookmark = BookmarkNode(
|
||||
type = BookmarkNodeType.ITEM,
|
||||
guid = "guid#${Math.random() * 1000}",
|
||||
parentGuid = null,
|
||||
position = null,
|
||||
title = null,
|
||||
url = "https://www.example.com",
|
||||
children = null
|
||||
)
|
||||
|
||||
controller.handleBookmarkClicked(bookmark)
|
||||
|
||||
verify {
|
||||
activity.openToBrowserAndLoad(
|
||||
searchTermOrURL = bookmark.url!!,
|
||||
newTab = true,
|
||||
from = BrowserDirection.FromHome
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN show all recently saved bookmark is clicked THEN the bookmarks root is opened`() {
|
||||
controller.handleShowAllBookmarksClicked()
|
||||
|
||||
val directions = HomeFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id)
|
||||
verify { navController.navigate(directions, any<NavOptions>()) }
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/* 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.recentbookmarks
|
||||
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
||||
import kotlinx.coroutines.test.TestCoroutineScope
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import mozilla.components.concept.storage.BookmarkNodeType
|
||||
import mozilla.components.support.test.libstate.ext.waitUntilIdle
|
||||
import mozilla.components.support.test.middleware.CaptureActionsMiddleware
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.components.bookmarks.BookmarksUseCase
|
||||
import org.mozilla.fenix.home.HomeFragmentAction
|
||||
import org.mozilla.fenix.home.HomeFragmentState
|
||||
import org.mozilla.fenix.home.HomeFragmentStore
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class RecentBookmarksFeatureTest {
|
||||
|
||||
private val middleware = CaptureActionsMiddleware<HomeFragmentState, HomeFragmentAction>()
|
||||
private val homeStore = HomeFragmentStore(middlewares = listOf(middleware))
|
||||
private val bookmarksUseCases: BookmarksUseCase = mockk(relaxed = true)
|
||||
private val scope = TestCoroutineScope()
|
||||
private val testDispatcher = TestCoroutineDispatcher()
|
||||
private val bookmark = BookmarkNode(
|
||||
type = BookmarkNodeType.ITEM,
|
||||
guid = "guid#${Math.random() * 1000}",
|
||||
parentGuid = null,
|
||||
position = null,
|
||||
title = null,
|
||||
url = "https://www.example.com",
|
||||
children = null
|
||||
)
|
||||
|
||||
@get:Rule
|
||||
val coroutinesTestRule = MainCoroutineRule(testDispatcher)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
coEvery { bookmarksUseCases.retrieveRecentBookmarks() }.coAnswers { listOf(bookmark) }
|
||||
}
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
scope.cleanupTestCoroutines()
|
||||
testDispatcher.cleanupTestCoroutines()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no recent bookmarks WHEN feature starts THEN fetch bookmarks and notify store`() =
|
||||
testDispatcher.runBlockingTest {
|
||||
val feature = RecentBookmarksFeature(
|
||||
homeStore,
|
||||
bookmarksUseCases,
|
||||
scope
|
||||
)
|
||||
|
||||
feature.start()
|
||||
|
||||
assertEquals(emptyList<BookmarkNode>(), homeStore.state.recentBookmarks)
|
||||
|
||||
testDispatcher.advanceUntilIdle()
|
||||
homeStore.waitUntilIdle()
|
||||
|
||||
coVerify {
|
||||
bookmarksUseCases.retrieveRecentBookmarks()
|
||||
}
|
||||
|
||||
middleware.assertLastAction(HomeFragmentAction.RecentBookmarksChange::class) {
|
||||
assertEquals(listOf(bookmark), it.recentBookmarks)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the feature is destroyed THEN the job is cancelled`() {
|
||||
val feature = spyk(RecentBookmarksFeature(
|
||||
homeStore,
|
||||
bookmarksUseCases,
|
||||
scope
|
||||
))
|
||||
|
||||
assertNull(feature.job)
|
||||
|
||||
feature.start()
|
||||
|
||||
assertNotNull(feature.job)
|
||||
|
||||
feature.stop()
|
||||
|
||||
verify(exactly = 1) { feature.stop() }
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/* 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.recentbookmarks.view
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import io.mockk.mockk
|
||||
import kotlinx.android.synthetic.main.recent_bookmark_item.view.*
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import mozilla.components.concept.storage.BookmarkNodeType
|
||||
import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert
|
||||
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 RecentBookmarkItemViewHolderTest {
|
||||
|
||||
private lateinit var view: View
|
||||
private lateinit var interactor: SessionControlInteractor
|
||||
|
||||
private val bookmarkNoUrl = BookmarkNode(
|
||||
type = BookmarkNodeType.ITEM,
|
||||
guid = "guid#${Math.random() * 1000}",
|
||||
parentGuid = null,
|
||||
position = null,
|
||||
title = "Bookmark Title",
|
||||
url = null,
|
||||
children = null
|
||||
)
|
||||
|
||||
private val bookmarkWithUrl = BookmarkNode(
|
||||
type = BookmarkNodeType.ITEM,
|
||||
guid = "guid#${Math.random() * 1000}",
|
||||
parentGuid = null,
|
||||
position = null,
|
||||
title = "Other Bookmark Title",
|
||||
url = "https://www.example.com",
|
||||
children = null
|
||||
)
|
||||
|
||||
private val bookmarkNoTitle = BookmarkNode(
|
||||
type = BookmarkNodeType.ITEM,
|
||||
guid = "guid#${Math.random() * 1000}",
|
||||
parentGuid = null,
|
||||
position = null,
|
||||
title = null,
|
||||
url = "https://www.github.com",
|
||||
children = null
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
view = LayoutInflater.from(testContext)
|
||||
.inflate(RecentBookmarkItemViewHolder.LAYOUT_ID, null)
|
||||
|
||||
interactor = mockk(relaxed = true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN a bookmark exists in the list THEN set the title text and subtitle from item`() {
|
||||
RecentBookmarkItemViewHolder(view, interactor).bind(bookmarkWithUrl)
|
||||
|
||||
val hostFromUrl = bookmarkWithUrl.url?.tryGetHostFromUrl()
|
||||
|
||||
Assert.assertEquals(bookmarkWithUrl.title, view.bookmark_title.text)
|
||||
Assert.assertEquals(hostFromUrl, view.bookmark_subtitle.text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN there is no url for the bookmark THEN do not load an icon `() {
|
||||
val viewHolder = RecentBookmarkItemViewHolder(view, interactor)
|
||||
|
||||
Assert.assertNull(view.favicon_image.drawable)
|
||||
|
||||
viewHolder.bind(bookmarkNoUrl)
|
||||
|
||||
Assert.assertNull(view.favicon_image.drawable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN a bookmark does not have a title THEN show the url`() {
|
||||
RecentBookmarkItemViewHolder(view, interactor).bind(bookmarkNoTitle)
|
||||
|
||||
Assert.assertEquals(bookmarkNoTitle.url, view.bookmark_title.text)
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/* 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.recentbookmarks.view
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.android.synthetic.main.recent_bookmarks_header.view.*
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import mozilla.components.concept.storage.BookmarkNodeType
|
||||
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 RecentBookmarksViewHolderTest {
|
||||
|
||||
private lateinit var view: View
|
||||
private lateinit var interactor: SessionControlInteractor
|
||||
|
||||
private val bookmark = BookmarkNode(
|
||||
type = BookmarkNodeType.ITEM,
|
||||
guid = "guid#${Math.random() * 1000}",
|
||||
parentGuid = null,
|
||||
position = null,
|
||||
title = null,
|
||||
url = null,
|
||||
children = null
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
view = LayoutInflater.from(testContext)
|
||||
.inflate(RecentBookmarksViewHolder.LAYOUT_ID, null)
|
||||
|
||||
interactor = mockk(relaxed = true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN show all bookmarks button is clicked THEN interactor is called`() {
|
||||
RecentBookmarksViewHolder(view, interactor).bind(listOf(bookmark))
|
||||
view.showAllBookmarksButton.performClick()
|
||||
|
||||
verify { interactor.onShowAllBookmarksClicked() }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue