1
0
Fork 0

For #12457 - Add MockK matcher for nav directions (#12262)

master
Tiger Oakes 2020-07-14 10:39:23 -07:00 committed by GitHub
parent f5f1a63690
commit 69020a1f26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 189 additions and 149 deletions

View File

@ -25,7 +25,6 @@ import mozilla.components.feature.addons.Addon
import mozilla.components.feature.addons.AddonManagerException import mozilla.components.feature.addons.AddonManagerException
import mozilla.components.feature.addons.ui.AddonInstallationDialogFragment import mozilla.components.feature.addons.ui.AddonInstallationDialogFragment
import mozilla.components.feature.addons.ui.AddonsManagerAdapter import mozilla.components.feature.addons.ui.AddonsManagerAdapter
import mozilla.components.feature.addons.ui.AddonsManagerAdapterDelegate
import mozilla.components.feature.addons.ui.PermissionsDialogFragment import mozilla.components.feature.addons.ui.PermissionsDialogFragment
import mozilla.components.feature.addons.ui.translatedName import mozilla.components.feature.addons.ui.translatedName
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -41,9 +40,9 @@ import java.util.concurrent.CancellationException
/** /**
* Fragment use for managing add-ons. * Fragment use for managing add-ons.
*/ */
@Suppress("TooManyFunctions", "LargeClass") @Suppress("TooManyFunctions")
class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management), class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) {
AddonsManagerAdapterDelegate {
/** /**
* Whether or not an add-on installation is in progress. * Whether or not an add-on installation is in progress.
*/ */
@ -67,23 +66,12 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management),
} }
} }
override fun onAddonItemClicked(addon: Addon) {
if (addon.isInstalled()) {
showInstalledAddonDetailsFragment(addon)
} else {
showDetailsFragment(addon)
}
}
override fun onInstallAddonButtonClicked(addon: Addon) {
showPermissionDialog(addon)
}
override fun onNotYetSupportedSectionClicked(unsupportedAddons: List<Addon>) {
showNotYetSupportedAddonFragment(ArrayList(unsupportedAddons))
}
private fun bindRecyclerView(view: View) { private fun bindRecyclerView(view: View) {
val managementView = AddonsManagementView(
navController = findNavController(),
showPermissionDialog = ::showPermissionDialog
)
val recyclerView = view.add_ons_list val recyclerView = view.add_ons_list
recyclerView.layoutManager = LinearLayoutManager(requireContext()) recyclerView.layoutManager = LinearLayoutManager(requireContext())
val shouldRefresh = adapter != null val shouldRefresh = adapter != null
@ -95,7 +83,7 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management),
if (!shouldRefresh) { if (!shouldRefresh) {
adapter = AddonsManagerAdapter( adapter = AddonsManagerAdapter(
requireContext().components.addonCollectionProvider, requireContext().components.addonCollectionProvider,
this@AddonsManagementFragment, managementView,
addons, addons,
style = createAddonStyle(requireContext()) style = createAddonStyle(requireContext())
) )
@ -137,30 +125,6 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management),
) )
} }
private fun showInstalledAddonDetailsFragment(addon: Addon) {
val directions =
AddonsManagementFragmentDirections.actionAddonsManagementFragmentToInstalledAddonDetails(
addon
)
findNavController().navigate(directions)
}
private fun showDetailsFragment(addon: Addon) {
val directions =
AddonsManagementFragmentDirections.actionAddonsManagementFragmentToAddonDetailsFragment(
addon
)
findNavController().navigate(directions)
}
private fun showNotYetSupportedAddonFragment(unsupportedAddons: ArrayList<Addon>) {
val directions =
AddonsManagementFragmentDirections.actionAddonsManagementFragmentToNotYetSupportedAddonFragment(
unsupportedAddons.toTypedArray()
)
findNavController().navigate(directions)
}
private fun findPreviousDialogFragment(): PermissionsDialogFragment? { private fun findPreviousDialogFragment(): PermissionsDialogFragment? {
return parentFragmentManager.findFragmentByTag(PERMISSIONS_DIALOG_FRAGMENT_TAG) as? PermissionsDialogFragment return parentFragmentManager.findFragmentByTag(PERMISSIONS_DIALOG_FRAGMENT_TAG) as? PermissionsDialogFragment
} }

View File

@ -0,0 +1,58 @@
/* 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.addons
import androidx.navigation.NavController
import mozilla.components.feature.addons.Addon
import mozilla.components.feature.addons.ui.AddonsManagerAdapterDelegate
/**
* View used for managing add-ons.
*/
class AddonsManagementView(
private val navController: NavController,
private val showPermissionDialog: (Addon) -> Unit
) : AddonsManagerAdapterDelegate {
override fun onAddonItemClicked(addon: Addon) {
if (addon.isInstalled()) {
showInstalledAddonDetailsFragment(addon)
} else {
showDetailsFragment(addon)
}
}
override fun onInstallAddonButtonClicked(addon: Addon) {
showPermissionDialog(addon)
}
override fun onNotYetSupportedSectionClicked(unsupportedAddons: List<Addon>) {
showNotYetSupportedAddonFragment(unsupportedAddons)
}
private fun showInstalledAddonDetailsFragment(addon: Addon) {
val directions =
AddonsManagementFragmentDirections.actionAddonsManagementFragmentToInstalledAddonDetails(
addon
)
navController.navigate(directions)
}
private fun showDetailsFragment(addon: Addon) {
val directions =
AddonsManagementFragmentDirections.actionAddonsManagementFragmentToAddonDetailsFragment(
addon
)
navController.navigate(directions)
}
private fun showNotYetSupportedAddonFragment(unsupportedAddons: List<Addon>) {
val directions =
AddonsManagementFragmentDirections.actionAddonsManagementFragmentToNotYetSupportedAddonFragment(
unsupportedAddons.toTypedArray()
)
navController.navigate(directions)
}
}

View File

@ -10,7 +10,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.Navigator
import androidx.navigation.fragment.NavHostFragment.findNavController import androidx.navigation.fragment.NavHostFragment.findNavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import org.mozilla.fenix.NavHostActivity import org.mozilla.fenix.NavHostActivity
@ -23,15 +22,7 @@ import org.mozilla.fenix.components.Components
val Fragment.requireComponents: Components val Fragment.requireComponents: Components
get() = requireContext().components get() = requireContext().components
fun Fragment.nav(@IdRes id: Int?, directions: NavDirections) { fun Fragment.nav(@IdRes id: Int?, directions: NavDirections, options: NavOptions? = null) {
findNavController(this).nav(id, directions)
}
fun Fragment.nav(@IdRes id: Int?, directions: NavDirections, extras: Navigator.Extras) {
findNavController(this).nav(id, directions, extras)
}
fun Fragment.nav(@IdRes id: Int?, directions: NavDirections, options: NavOptions) {
findNavController(this).nav(id, directions, options) findNavController(this).nav(id, directions, options)
} }

View File

@ -4,12 +4,10 @@
package org.mozilla.fenix.ext package org.mozilla.fenix.ext
import android.os.Bundle
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.Navigator
import io.sentry.Sentry import io.sentry.Sentry
import org.mozilla.fenix.components.isSentryEnabled import org.mozilla.fenix.components.isSentryEnabled
@ -25,35 +23,6 @@ fun NavController.nav(@IdRes id: Int?, directions: NavDirections, navOptions: Na
} }
} }
fun NavController.nav(@IdRes id: Int?, directions: NavDirections, extras: Navigator.Extras) {
if (id == null || this.currentDestination?.id == id) {
this.navigate(directions, extras)
} else {
recordIdException(this.currentDestination?.id, id)
}
}
fun NavController.nav(
@IdRes id: Int?,
directions: NavDirections,
navOptions: NavOptions? = null,
extras: Navigator.Extras? = null
) = nav(id, directions.actionId, directions.arguments, navOptions, extras)
fun NavController.nav(
@IdRes id: Int?,
@IdRes destId: Int,
args: Bundle?,
navOptions: NavOptions?,
extras: Navigator.Extras?
) {
if (id == null || this.currentDestination?.id == id) {
this.navigate(destId, args, navOptions, extras)
} else {
recordIdException(this.currentDestination?.id, id)
}
}
fun NavController.alreadyOnDestination(@IdRes destId: Int?): Boolean { fun NavController.alreadyOnDestination(@IdRes destId: Int?): Boolean {
return destId?.let { currentDestination?.id == it || popBackStack(it, false) } ?: false return destId?.let { currentDestination?.id == it || popBackStack(it, false) } ?: false
} }

View File

@ -0,0 +1,81 @@
/* 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.addons
import androidx.navigation.NavController
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.feature.addons.Addon
import mozilla.components.feature.addons.ui.AddonsManagerAdapterDelegate
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.addons.AddonsManagementFragmentDirections.Companion.actionAddonsManagementFragmentToInstalledAddonDetails
import org.mozilla.fenix.ext.directionsEq
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class AddonsManagementViewTest {
@RelaxedMockK private lateinit var navController: NavController
@RelaxedMockK private lateinit var showPermissionDialog: (Addon) -> Unit
private lateinit var managementView: AddonsManagerAdapterDelegate
@Before
fun setup() {
MockKAnnotations.init(this)
managementView = AddonsManagementView(navController, showPermissionDialog)
}
@Test
fun `onAddonItemClicked shows installed details if addon is installed`() {
val addon = mockk<Addon> {
every { isInstalled() } returns true
}
managementView.onAddonItemClicked(addon)
verify {
navController.navigate(
directionsEq(actionAddonsManagementFragmentToInstalledAddonDetails(addon))
)
}
}
@Test
fun `onAddonItemClicked shows details if addon is not installed`() {
val addon = mockk<Addon> {
every { isInstalled() } returns false
}
managementView.onAddonItemClicked(addon)
val expected = AddonsManagementFragmentDirections.actionAddonsManagementFragmentToAddonDetailsFragment(addon)
verify {
navController.navigate(directionsEq(expected))
}
}
@Test
fun `onInstallAddonButtonClicked shows permission dialog`() {
val addon = mockk<Addon>()
managementView.onInstallAddonButtonClicked(addon)
verify { showPermissionDialog(addon) }
}
@Test
fun `onNotYetSupportedSectionClicked shows not yet supported fragment`() {
val addons = listOf<Addon>(mockk(), mockk())
managementView.onNotYetSupportedSectionClicked(addons)
val expected = AddonsManagementFragmentDirections.actionAddonsManagementFragmentToNotYetSupportedAddonFragment(
addons.toTypedArray()
)
verify {
navController.navigate(directionsEq(expected))
}
}
}

View File

@ -6,7 +6,6 @@ package org.mozilla.fenix.components.toolbar
import android.content.Intent import android.content.Intent
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.Runs import io.mockk.Runs
@ -32,6 +31,7 @@ import mozilla.components.browser.state.state.ReaderState
import mozilla.components.browser.state.state.createTab import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.EngineView
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.search.SearchUseCases import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionFeature import mozilla.components.feature.session.SessionFeature
import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.session.SessionUseCases
@ -46,6 +46,7 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator import org.mozilla.fenix.browser.BrowserAnimator
import org.mozilla.fenix.browser.BrowserFragmentDirections import org.mozilla.fenix.browser.BrowserFragmentDirections
@ -61,6 +62,7 @@ import org.mozilla.fenix.components.TopSiteStorage
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.directionsEq
import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.toTab import org.mozilla.fenix.ext.toTab
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@ -384,12 +386,20 @@ class DefaultBrowserToolbarControllerTest {
val item = ToolbarMenu.Item.Share val item = ToolbarMenu.Item.Share
every { currentSession.url } returns "https://mozilla.org" every { currentSession.url } returns "https://mozilla.org"
every { currentSession.title } returns "Mozilla"
val controller = createController(scope = this) val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item) controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SHARE)) } verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SHARE)) }
verify { navController.navigate(any<NavDirections>()) } verify {
navController.navigate(
directionsEq(NavGraphDirections.actionGlobalShareFragment(
data = arrayOf(ShareData(url = "https://mozilla.org", title = "Mozilla")),
showPage = true
))
)
}
} }
@Test @Test

View File

@ -56,16 +56,6 @@ class FragmentTest {
confirmVerified(mockFragment) confirmVerified(mockFragment)
} }
@Test
fun `Test nav fun with ID, directions, and extras`() {
every { (NavHostFragment.findNavController(mockFragment).navigate(navDirections, mockExtras)) } just Runs
mockFragment.nav(mockId, navDirections, mockExtras)
verify { (NavHostFragment.findNavController(mockFragment).currentDestination) }
verify { (NavHostFragment.findNavController(mockFragment).navigate(navDirections, mockExtras)) }
confirmVerified(mockFragment)
}
@Test @Test
fun `Test nav fun with ID, directions, and options`() { fun `Test nav fun with ID, directions, and options`() {
every { (NavHostFragment.findNavController(mockFragment).navigate(navDirections, mockOptions)) } just Runs every { (NavHostFragment.findNavController(mockFragment).navigate(navDirections, mockOptions)) } just Runs

View File

@ -0,0 +1,21 @@
package org.mozilla.fenix.ext
import androidx.navigation.NavDirections
import io.mockk.Matcher
import io.mockk.MockKMatcherScope
import io.mockk.internalSubstitute
import mozilla.components.support.ktx.android.os.contentEquals
/**
* Verify that an equal [NavDirections] object was passed in a MockK verify call.
*/
fun MockKMatcherScope.directionsEq(value: NavDirections) = match(EqNavDirectionsMatcher(value))
private data class EqNavDirectionsMatcher(private val value: NavDirections) : Matcher<NavDirections> {
override fun match(arg: NavDirections?): Boolean =
value.actionId == arg?.actionId && value.arguments contentEquals arg.arguments
override fun substitute(map: Map<Any, Any>) =
copy(value = value.internalSubstitute(map))
}

View File

@ -4,12 +4,10 @@
package org.mozilla.fenix.ext package org.mozilla.fenix.ext
import android.os.Bundle
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.Navigator.Extras
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.Runs import io.mockk.Runs
import io.mockk.confirmVerified import io.mockk.confirmVerified
@ -29,9 +27,7 @@ class NavControllerTest {
@MockK(relaxUnitFun = true) private lateinit var navController: NavController @MockK(relaxUnitFun = true) private lateinit var navController: NavController
@MockK private lateinit var navDirections: NavDirections @MockK private lateinit var navDirections: NavDirections
@MockK private lateinit var mockDestination: NavDestination @MockK private lateinit var mockDestination: NavDestination
@MockK private lateinit var mockExtras: Extras
@MockK private lateinit var mockOptions: NavOptions @MockK private lateinit var mockOptions: NavOptions
@MockK private lateinit var mockBundle: Bundle
@Before @Before
fun setUp() { fun setUp() {
@ -51,13 +47,6 @@ class NavControllerTest {
verify { navController.navigate(navDirections, null) } verify { navController.navigate(navDirections, null) }
} }
@Test
fun `Nav with id, directions, and extras args`() {
navController.nav(currentDestId, navDirections, mockExtras)
verify { navController.currentDestination }
verify { navController.navigate(navDirections, mockExtras) }
}
@Test @Test
fun `Nav with id, directions, and options args`() { fun `Nav with id, directions, and options args`() {
navController.nav(currentDestId, navDirections, mockOptions) navController.nav(currentDestId, navDirections, mockOptions)
@ -65,23 +54,6 @@ class NavControllerTest {
verify { navController.navigate(navDirections, mockOptions) } verify { navController.navigate(navDirections, mockOptions) }
} }
@Test
fun `Nav with id, directions, options, and extras args`() {
every { navDirections.actionId } returns 5
every { navDirections.arguments } returns mockBundle
navController.nav(currentDestId, navDirections, mockOptions, mockExtras)
verify { navController.currentDestination }
verify { navController.navigate(5, mockBundle, mockOptions, mockExtras) }
}
@Test
fun `Nav with id, destId, bundle, options, and extras args`() {
navController.nav(currentDestId, 5, mockBundle, mockOptions, mockExtras)
verify { navController.currentDestination }
verify { navController.navigate(5, mockBundle, mockOptions, mockExtras) }
}
@Test @Test
fun `Test error response for id exception in-block`() { fun `Test error response for id exception in-block`() {
navController.nav(7, navDirections) navController.nav(7, navDirections)
@ -90,15 +62,6 @@ class NavControllerTest {
confirmVerified(navController) confirmVerified(navController)
} }
@Test
fun `Test error response for null current destination`() {
every { navController.currentDestination } returns null
navController.nav(7, navDirections, mockExtras)
verify { navController.currentDestination }
verify { Sentry.capture("Fragment id null did not match expected 7") }
confirmVerified(navController)
}
@Test @Test
fun `Test record id exception fun`() { fun `Test record id exception fun`() {
val actual = 7 val actual = 7

View File

@ -8,7 +8,6 @@ import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.res.Resources import android.content.res.Resources
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDirections
import io.mockk.coVerifyOrder import io.mockk.coVerifyOrder
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
@ -24,7 +23,6 @@ import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@ -189,8 +187,6 @@ class HistoryControllerTest {
@Test @Test
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun onShareItem() { fun onShareItem() {
val directions = slot<NavDirections>()
controller.handleShare(historyItem) controller.handleShare(historyItem)
// `verify` checks for referential equality. // `verify` checks for referential equality.
@ -198,17 +194,11 @@ class HistoryControllerTest {
// Capture the NavDirections and `assert` for structural equality after. // Capture the NavDirections and `assert` for structural equality after.
verify { verify {
navController.navigate( navController.navigate(
capture(directions) HistoryFragmentDirections.actionGlobalShareFragment(
data = arrayOf(ShareData(url = historyItem.url, title = historyItem.title))
)
) )
} }
assertEquals(
directions.captured.actionId,
R.id.action_global_shareFragment
)
assertEquals(1, (directions.captured.arguments["data"] as Array<ShareData>).size)
assertEquals(historyItem.title, (directions.captured.arguments["data"] as Array<ShareData>)[0].title)
assertEquals(historyItem.url, (directions.captured.arguments["data"] as Array<ShareData>)[0].url)
} }
@Test @Test

View File

@ -27,6 +27,7 @@ import org.junit.Assert.assertSame
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.components.PermissionStorage import org.mozilla.fenix.components.PermissionStorage
import org.mozilla.fenix.ext.directionsEq
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled
@ -136,7 +137,9 @@ class DefaultQuickSettingsControllerTest {
invalidSitePermissionsController.handlePermissionToggled(websitePermission) invalidSitePermissionsController.handlePermissionToggled(websitePermission)
verify { verify {
navController.navigate(any<NavDirections>()) navController.navigate(directionsEq(
QuickSettingsSheetDialogFragmentDirections.actionGlobalSitePermissionsManagePhoneFeature(PhoneFeature.CAMERA)
))
} }
} }