Fix intermittent test failures in BookmarkControllerTest

This commit is contained in:
Christian Sadilek 2021-07-28 12:13:04 -04:00 committed by mergify[bot]
parent f03f7c6071
commit d0c75740b7

View File

@ -4,7 +4,6 @@
package org.mozilla.fenix.library.bookmarks
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import androidx.navigation.NavController
@ -30,6 +29,7 @@ import mozilla.components.concept.storage.BookmarkNodeType
import mozilla.components.feature.tabs.TabsUseCases
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.BrowserDirection
@ -45,8 +45,6 @@ import org.mozilla.fenix.ext.components
@ExperimentalCoroutinesApi
class BookmarkControllerTest {
private lateinit var controller: BookmarkController
private val bookmarkStore = spyk(BookmarkFragmentStore(BookmarkFragmentState(null)))
private val context: Context = mockk(relaxed = true)
private val scope = TestCoroutineScope()
@ -54,13 +52,6 @@ class BookmarkControllerTest {
private val navController: NavController = mockk(relaxed = true)
private val sharedViewModel: BookmarksSharedViewModel = mockk()
private val tabsUseCases: TabsUseCases = mockk()
private val loadBookmarkNode: suspend (String) -> BookmarkNode? = mockk(relaxed = true)
private val showSnackbar: (String) -> Unit = mockk(relaxed = true)
private val deleteBookmarkNodes: (Set<BookmarkNode>, Event) -> Unit = mockk(relaxed = true)
private val deleteBookmarkFolder: (Set<BookmarkNode>) -> Unit = mockk(relaxed = true)
private val invokePendingDeletion: () -> Unit = mockk(relaxed = true)
private val showTabTray: () -> Unit = mockk(relaxed = true)
private val homeActivity: HomeActivity = mockk(relaxed = true)
private val services: Services = mockk(relaxed = true)
private val addNewTabUseCase: TabsUseCases.AddNewTabUseCase = mockk(relaxed = true)
@ -102,8 +93,295 @@ class BookmarkControllerTest {
every { bookmarkStore.dispatch(any()) } returns mockk()
every { sharedViewModel.selectedFolder = any() } just runs
every { tabsUseCases.addTab } returns addNewTabUseCase
}
controller = DefaultBookmarkController(
@After
fun cleanUp() {
scope.cleanupTestCoroutines()
}
@Test
fun `handleBookmarkChanged updates the selected bookmark node`() {
createController().handleBookmarkChanged(tree)
verify {
sharedViewModel.selectedFolder = tree
bookmarkStore.dispatch(BookmarkFragmentAction.Change(tree))
}
}
@Test
fun `handleBookmarkTapped should load the bookmark in a new tab`() {
var invokePendingDeletionInvoked = false
createController(invokePendingDeletion = {
invokePendingDeletionInvoked = true
}).handleBookmarkTapped(item)
assertTrue(invokePendingDeletionInvoked)
verify {
homeActivity.openToBrowserAndLoad(item.url!!, true, BrowserDirection.FromBookmarks)
}
}
@Test
fun `handleBookmarkTapped should respect browsing mode`() {
// if in normal mode, should be in normal mode
every { homeActivity.browsingModeManager.mode } returns BrowsingMode.Normal
val controller = createController()
controller.handleBookmarkTapped(item)
assertEquals(BrowsingMode.Normal, homeActivity.browsingModeManager.mode)
// if in private mode, should be in private mode
every { homeActivity.browsingModeManager.mode } returns BrowsingMode.Private
controller.handleBookmarkTapped(item)
assertEquals(BrowsingMode.Private, homeActivity.browsingModeManager.mode)
}
@Test
fun `handleBookmarkExpand clears selection and invokes pending deletions`() {
var invokePendingDeletionInvoked = false
createController(invokePendingDeletion = {
invokePendingDeletionInvoked = true
}).handleBookmarkExpand(tree)
assertTrue(invokePendingDeletionInvoked)
}
@Test
fun `handleBookmarkExpand should refresh and change the active bookmark node`() {
var loadBookmarkNodeInvoked = false
createController(loadBookmarkNode = {
loadBookmarkNodeInvoked = true
tree
}).handleBookmarkExpand(tree)
assertTrue(loadBookmarkNodeInvoked)
coVerify {
sharedViewModel.selectedFolder = tree
bookmarkStore.dispatch(BookmarkFragmentAction.Change(tree))
}
}
@Test
fun `handleSelectionModeSwitch should invalidateOptionsMenu`() {
createController().handleSelectionModeSwitch()
verify {
homeActivity.invalidateOptionsMenu()
}
}
@Test
fun `handleBookmarkEdit should navigate to the 'Edit' fragment`() {
var invokePendingDeletionInvoked = false
createController(invokePendingDeletion = {
invokePendingDeletionInvoked = true
}).handleBookmarkEdit(item)
assertTrue(invokePendingDeletionInvoked)
verify {
navController.navigate(
BookmarkFragmentDirections.actionBookmarkFragmentToBookmarkEditFragment(
item.guid
),
null
)
}
}
@Test
fun `handleBookmarkSelected dispatches Select action when selecting a non-root folder`() {
createController().handleBookmarkSelected(item)
verify {
bookmarkStore.dispatch(BookmarkFragmentAction.Select(item))
}
}
@Test
fun `handleBookmarkSelected should show a toast when selecting a root folder`() {
val errorMessage = context.getString(R.string.bookmark_cannot_edit_root)
var showSnackbarInvoked = false
createController(showSnackbar = {
assertEquals(errorMessage, it)
showSnackbarInvoked = true
}).handleBookmarkSelected(root)
assertTrue(showSnackbarInvoked)
}
@Test
fun `handleBookmarkSelected does not select in Syncing mode`() {
every { bookmarkStore.state.mode } returns BookmarkFragmentState.Mode.Syncing
createController().handleBookmarkSelected(item)
verify { bookmarkStore.dispatch(BookmarkFragmentAction.Select(item)) wasNot called }
}
@Test
fun `handleBookmarkDeselected dispatches Deselect action`() {
createController().handleBookmarkDeselected(item)
verify {
bookmarkStore.dispatch(BookmarkFragmentAction.Deselect(item))
}
}
@Test
fun `handleCopyUrl should copy bookmark url to clipboard and show a toast`() {
val urlCopiedMessage = context.getString(R.string.url_copied)
var showSnackbarInvoked = false
createController(showSnackbar = {
assertEquals(urlCopiedMessage, it)
showSnackbarInvoked = true
}).handleCopyUrl(item)
assertTrue(showSnackbarInvoked)
}
@Test
fun `handleBookmarkSharing should navigate to the 'Share' fragment`() {
val navDirectionsSlot = slot<NavDirections>()
every { navController.navigate(capture(navDirectionsSlot), null) } just Runs
createController().handleBookmarkSharing(item)
verify {
navController.navigate(navDirectionsSlot.captured, null)
}
}
@Test
fun `handleBookmarkTapped should open the bookmark`() {
var invokePendingDeletionInvoked = false
createController(invokePendingDeletion = {
invokePendingDeletionInvoked = true
}).handleBookmarkTapped(item)
assertTrue(invokePendingDeletionInvoked)
verify {
homeActivity.openToBrowserAndLoad(item.url!!, true, BrowserDirection.FromBookmarks)
}
}
@Test
fun `handleOpeningBookmark should open the bookmark a new 'Normal' tab`() {
var invokePendingDeletionInvoked = false
var showTabTrayInvoked = false
createController(invokePendingDeletion = {
invokePendingDeletionInvoked = true
}, showTabTray = {
showTabTrayInvoked = true
}
).handleOpeningBookmark(item, BrowsingMode.Normal)
assertTrue(invokePendingDeletionInvoked)
assertTrue(showTabTrayInvoked)
verifyOrder {
homeActivity.browsingModeManager.mode = BrowsingMode.Normal
addNewTabUseCase.invoke(item.url!!, private = false)
}
}
@Test
fun `handleOpeningBookmark should open the bookmark a new 'Private' tab`() {
var invokePendingDeletionInvoked = false
var showTabTrayInvoked = false
createController(invokePendingDeletion = {
invokePendingDeletionInvoked = true
}, showTabTray = {
showTabTrayInvoked = true
}
).handleOpeningBookmark(item, BrowsingMode.Private)
assertTrue(invokePendingDeletionInvoked)
assertTrue(showTabTrayInvoked)
verifyOrder {
homeActivity.browsingModeManager.mode = BrowsingMode.Private
addNewTabUseCase.invoke(item.url!!, private = true)
}
}
@Test
fun `handleBookmarkDeletion for an item should properly call a passed in delegate`() {
var deleteBookmarkNodesInvoked = false
createController(deleteBookmarkNodes = { nodes, event ->
assertEquals(setOf(item), nodes)
assertEquals(Event.RemoveBookmark, event)
deleteBookmarkNodesInvoked = true
}).handleBookmarkDeletion(setOf(item), Event.RemoveBookmark)
assertTrue(deleteBookmarkNodesInvoked)
}
@Test
fun `handleBookmarkDeletion for multiple bookmarks should properly call a passed in delegate`() {
var deleteBookmarkNodesInvoked = false
createController(deleteBookmarkNodes = { nodes, event ->
assertEquals(setOf(item, subfolder), nodes)
assertEquals(Event.RemoveBookmarks, event)
deleteBookmarkNodesInvoked = true
}).handleBookmarkDeletion(setOf(item, subfolder), Event.RemoveBookmarks)
assertTrue(deleteBookmarkNodesInvoked)
}
@Test
fun `handleBookmarkDeletion for a folder should properly call the delete folder delegate`() {
var deleteBookmarkFolderInvoked = false
createController(deleteBookmarkFolder = { nodes ->
assertEquals(setOf(subfolder), nodes)
deleteBookmarkFolderInvoked = true
}).handleBookmarkFolderDeletion(setOf(subfolder))
assertTrue(deleteBookmarkFolderInvoked)
}
@Test
fun `handleRequestSync dispatches actions in the correct order`() {
every { homeActivity.components.backgroundServices.accountManager } returns mockk(relaxed = true)
coEvery { homeActivity.bookmarkStorage.getBookmark(any()) } returns tree
createController().handleRequestSync()
verifyOrder {
bookmarkStore.dispatch(BookmarkFragmentAction.StartSync)
bookmarkStore.dispatch(BookmarkFragmentAction.FinishSync)
}
}
@Test
fun `handleBackPressed with one item in backstack should trigger handleBackPressed in NavController`() {
every { bookmarkStore.state.guidBackstack } returns listOf(tree.guid)
every { bookmarkStore.state.tree } returns tree
var invokePendingDeletionInvoked = false
createController(invokePendingDeletion = {
invokePendingDeletionInvoked = true
}).handleBackPressed()
assertTrue(invokePendingDeletionInvoked)
verify {
navController.popBackStack()
}
}
@Suppress("LongParameterList")
private fun createController(
loadBookmarkNode: suspend (String) -> BookmarkNode? = { _ -> null },
showSnackbar: (String) -> Unit = { _ -> },
deleteBookmarkNodes: (Set<BookmarkNode>, Event) -> Unit = { _, _ -> },
deleteBookmarkFolder: (Set<BookmarkNode>) -> Unit = { _ -> },
invokePendingDeletion: () -> Unit = { },
showTabTray: () -> Unit = { }
): BookmarkController {
return DefaultBookmarkController(
activity = homeActivity,
navController = navController,
clipboardManager = clipboardManager,
@ -119,243 +397,4 @@ class BookmarkControllerTest {
showTabTray = showTabTray
)
}
@After
fun cleanUp() {
scope.cleanupTestCoroutines()
}
@Test
fun `handleBookmarkChanged updates the selected bookmark node`() {
controller.handleBookmarkChanged(tree)
verify {
sharedViewModel.selectedFolder = tree
bookmarkStore.dispatch(BookmarkFragmentAction.Change(tree))
}
}
@Test
fun `handleBookmarkTapped should load the bookmark in a new tab`() {
controller.handleBookmarkTapped(item)
verifyOrder {
invokePendingDeletion.invoke()
homeActivity.openToBrowserAndLoad(item.url!!, true, BrowserDirection.FromBookmarks)
}
}
@Test
fun `handleBookmarkTapped should respect browsing mode`() {
// if in normal mode, should be in normal mode
every { homeActivity.browsingModeManager.mode } returns BrowsingMode.Normal
controller.handleBookmarkTapped(item)
assertEquals(BrowsingMode.Normal, homeActivity.browsingModeManager.mode)
// if in private mode, should be in private mode
every { homeActivity.browsingModeManager.mode } returns BrowsingMode.Private
controller.handleBookmarkTapped(item)
assertEquals(BrowsingMode.Private, homeActivity.browsingModeManager.mode)
}
@Test
fun `handleBookmarkExpand clears selection and invokes pending deletions`() {
coEvery { loadBookmarkNode.invoke(any()) } returns tree
controller.handleBookmarkExpand(tree)
verify {
invokePendingDeletion.invoke()
controller.handleAllBookmarksDeselected()
}
}
@Test
fun `handleBookmarkExpand should refresh and change the active bookmark node`() {
coEvery { loadBookmarkNode.invoke(any()) } returns tree
controller.handleBookmarkExpand(tree)
coVerify {
loadBookmarkNode.invoke(tree.guid)
sharedViewModel.selectedFolder = tree
bookmarkStore.dispatch(BookmarkFragmentAction.Change(tree))
}
}
@Test
fun `handleSelectionModeSwitch should invalidateOptionsMenu`() {
controller.handleSelectionModeSwitch()
verify {
homeActivity.invalidateOptionsMenu()
}
}
@Test
fun `handleBookmarkEdit should navigate to the 'Edit' fragment`() {
controller.handleBookmarkEdit(item)
verify {
invokePendingDeletion.invoke()
navController.navigate(
BookmarkFragmentDirections.actionBookmarkFragmentToBookmarkEditFragment(
item.guid
),
null
)
}
}
@Test
fun `handleBookmarkSelected dispatches Select action when selecting a non-root folder`() {
controller.handleBookmarkSelected(item)
verify {
bookmarkStore.dispatch(BookmarkFragmentAction.Select(item))
}
}
@Test
fun `handleBookmarkSelected should show a toast when selecting a root folder`() {
val errorMessage = context.getString(R.string.bookmark_cannot_edit_root)
controller.handleBookmarkSelected(root)
verify {
showSnackbar(errorMessage)
}
}
@Test
fun `handleBookmarkSelected does not select in Syncing mode`() {
every { bookmarkStore.state.mode } returns BookmarkFragmentState.Mode.Syncing
controller.handleBookmarkSelected(item)
verify { bookmarkStore.dispatch(BookmarkFragmentAction.Select(item)) wasNot called }
}
@Test
fun `handleBookmarkDeselected dispatches Deselect action`() {
controller.handleBookmarkDeselected(item)
verify {
bookmarkStore.dispatch(BookmarkFragmentAction.Deselect(item))
}
}
@Test
fun `handleCopyUrl should copy bookmark url to clipboard and show a toast`() {
val urlCopiedMessage = context.getString(R.string.url_copied)
controller.handleCopyUrl(item)
verifyOrder {
ClipData.newPlainText(item.url, item.url)
showSnackbar(urlCopiedMessage)
}
}
@Test
fun `handleBookmarkSharing should navigate to the 'Share' fragment`() {
val navDirectionsSlot = slot<NavDirections>()
every { navController.navigate(capture(navDirectionsSlot), null) } just Runs
controller.handleBookmarkSharing(item)
verify {
navController.navigate(navDirectionsSlot.captured, null)
}
}
@Test
fun `handleBookmarkTapped should open the bookmark`() {
controller.handleBookmarkTapped(item)
verifyOrder {
invokePendingDeletion.invoke()
homeActivity.openToBrowserAndLoad(item.url!!, true, BrowserDirection.FromBookmarks)
}
}
@Test
fun `handleOpeningBookmark should open the bookmark a new 'Normal' tab`() {
controller.handleOpeningBookmark(item, BrowsingMode.Normal)
verifyOrder {
invokePendingDeletion.invoke()
homeActivity.browsingModeManager.mode = BrowsingMode.Normal
addNewTabUseCase.invoke(item.url!!, private = false)
showTabTray
}
}
@Test
fun `handleOpeningBookmark should open the bookmark a new 'Private' tab`() {
controller.handleOpeningBookmark(item, BrowsingMode.Private)
verifyOrder {
invokePendingDeletion.invoke()
homeActivity.browsingModeManager.mode = BrowsingMode.Private
addNewTabUseCase.invoke(item.url!!, private = true)
showTabTray
}
}
@Test
fun `handleBookmarkDeletion for an item should properly call a passed in delegate`() {
controller.handleBookmarkDeletion(setOf(item), Event.RemoveBookmark)
verify {
deleteBookmarkNodes(setOf(item), Event.RemoveBookmark)
}
}
@Test
fun `handleBookmarkDeletion for multiple bookmarks should properly call a passed in delegate`() {
controller.handleBookmarkDeletion(setOf(item, subfolder), Event.RemoveBookmarks)
verify {
deleteBookmarkNodes(setOf(item, subfolder), Event.RemoveBookmarks)
}
}
@Test
fun `handleBookmarkDeletion for a folder should properly call the delete folder delegate`() {
controller.handleBookmarkFolderDeletion(setOf(subfolder))
verify {
deleteBookmarkFolder(setOf(subfolder))
}
}
@Test
fun `handleRequestSync dispatches actions in the correct order`() {
every { homeActivity.components.backgroundServices.accountManager } returns mockk(relaxed = true)
coEvery { homeActivity.bookmarkStorage.getBookmark(any()) } returns tree
coEvery { loadBookmarkNode.invoke(any()) } returns tree
controller.handleRequestSync()
verifyOrder {
bookmarkStore.dispatch(BookmarkFragmentAction.StartSync)
bookmarkStore.dispatch(BookmarkFragmentAction.FinishSync)
}
}
@Test
fun `handleBackPressed with one item in backstack should trigger handleBackPressed in NavController`() {
every { bookmarkStore.state.guidBackstack } returns listOf(tree.guid)
every { bookmarkStore.state.tree } returns tree
controller.handleBackPressed()
verify {
invokePendingDeletion.invoke()
navController.popBackStack()
}
}
}