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.ui.AddonInstallationDialogFragment
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.translatedName
import org.mozilla.fenix.R
@ -41,9 +40,9 @@ import java.util.concurrent.CancellationException
/**
* Fragment use for managing add-ons.
*/
@Suppress("TooManyFunctions", "LargeClass")
class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management),
AddonsManagerAdapterDelegate {
@Suppress("TooManyFunctions")
class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) {
/**
* 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) {
val managementView = AddonsManagementView(
navController = findNavController(),
showPermissionDialog = ::showPermissionDialog
)
val recyclerView = view.add_ons_list
recyclerView.layoutManager = LinearLayoutManager(requireContext())
val shouldRefresh = adapter != null
@ -95,7 +83,7 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management),
if (!shouldRefresh) {
adapter = AddonsManagerAdapter(
requireContext().components.addonCollectionProvider,
this@AddonsManagementFragment,
managementView,
addons,
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? {
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.navigation.NavDirections
import androidx.navigation.NavOptions
import androidx.navigation.Navigator
import androidx.navigation.fragment.NavHostFragment.findNavController
import androidx.navigation.fragment.findNavController
import org.mozilla.fenix.NavHostActivity
@ -23,15 +22,7 @@ import org.mozilla.fenix.components.Components
val Fragment.requireComponents: Components
get() = requireContext().components
fun Fragment.nav(@IdRes id: Int?, directions: NavDirections) {
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) {
fun Fragment.nav(@IdRes id: Int?, directions: NavDirections, options: NavOptions? = null) {
findNavController(this).nav(id, directions, options)
}

View File

@ -4,12 +4,10 @@
package org.mozilla.fenix.ext
import android.os.Bundle
import androidx.annotation.IdRes
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.NavOptions
import androidx.navigation.Navigator
import io.sentry.Sentry
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 {
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 androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import io.mockk.MockKAnnotations
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.store.BrowserStore
import mozilla.components.concept.engine.EngineView
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionFeature
import mozilla.components.feature.session.SessionUseCases
@ -46,6 +46,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator
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.MetricController
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.directionsEq
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.toTab
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@ -384,12 +386,20 @@ class DefaultBrowserToolbarControllerTest {
val item = ToolbarMenu.Item.Share
every { currentSession.url } returns "https://mozilla.org"
every { currentSession.title } returns "Mozilla"
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
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

View File

@ -56,16 +56,6 @@ class FragmentTest {
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
fun `Test nav fun with ID, directions, and options`() {
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
import android.os.Bundle
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.NavDirections
import androidx.navigation.NavOptions
import androidx.navigation.Navigator.Extras
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.confirmVerified
@ -29,9 +27,7 @@ class NavControllerTest {
@MockK(relaxUnitFun = true) private lateinit var navController: NavController
@MockK private lateinit var navDirections: NavDirections
@MockK private lateinit var mockDestination: NavDestination
@MockK private lateinit var mockExtras: Extras
@MockK private lateinit var mockOptions: NavOptions
@MockK private lateinit var mockBundle: Bundle
@Before
fun setUp() {
@ -51,13 +47,6 @@ class NavControllerTest {
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
fun `Nav with id, directions, and options args`() {
navController.nav(currentDestId, navDirections, mockOptions)
@ -65,23 +54,6 @@ class NavControllerTest {
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
fun `Test error response for id exception in-block`() {
navController.nav(7, navDirections)
@ -90,15 +62,6 @@ class NavControllerTest {
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
fun `Test record id exception fun`() {
val actual = 7

View File

@ -8,7 +8,6 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.res.Resources
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import io.mockk.coVerifyOrder
import io.mockk.every
import io.mockk.mockk
@ -24,7 +23,6 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@ -189,8 +187,6 @@ class HistoryControllerTest {
@Test
@Suppress("UNCHECKED_CAST")
fun onShareItem() {
val directions = slot<NavDirections>()
controller.handleShare(historyItem)
// `verify` checks for referential equality.
@ -198,17 +194,11 @@ class HistoryControllerTest {
// Capture the NavDirections and `assert` for structural equality after.
verify {
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

View File

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