From 69020a1f269c57e86fb4eb1dfd25d3ea168b5a29 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Tue, 14 Jul 2020 10:39:23 -0700 Subject: [PATCH] For #12457 - Add MockK matcher for nav directions (#12262) --- .../fenix/addons/AddonsManagementFragment.kt | 54 +++---------- .../fenix/addons/AddonsManagementView.kt | 58 +++++++++++++ .../java/org/mozilla/fenix/ext/Fragment.kt | 11 +-- .../org/mozilla/fenix/ext/NavController.kt | 31 ------- .../fenix/addons/AddonsManagementViewTest.kt | 81 +++++++++++++++++++ .../DefaultBrowserToolbarControllerTest.kt | 14 +++- .../org/mozilla/fenix/ext/FragmentTest.kt | 10 --- .../mozilla/fenix/ext/MockKMatcherScope.kt | 21 +++++ .../mozilla/fenix/ext/NavControllerTest.kt | 37 --------- .../library/history/HistoryControllerTest.kt | 16 +--- .../DefaultQuickSettingsControllerTest.kt | 5 +- 11 files changed, 189 insertions(+), 149 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/addons/AddonsManagementView.kt create mode 100644 app/src/test/java/org/mozilla/fenix/addons/AddonsManagementViewTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/ext/MockKMatcherScope.kt diff --git a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt index 166ec2a39..ce2a4188a 100644 --- a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt @@ -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) { - 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) { - val directions = - AddonsManagementFragmentDirections.actionAddonsManagementFragmentToNotYetSupportedAddonFragment( - unsupportedAddons.toTypedArray() - ) - findNavController().navigate(directions) - } - private fun findPreviousDialogFragment(): PermissionsDialogFragment? { return parentFragmentManager.findFragmentByTag(PERMISSIONS_DIALOG_FRAGMENT_TAG) as? PermissionsDialogFragment } diff --git a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementView.kt b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementView.kt new file mode 100644 index 000000000..2a6370ed6 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementView.kt @@ -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) { + 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) { + val directions = + AddonsManagementFragmentDirections.actionAddonsManagementFragmentToNotYetSupportedAddonFragment( + unsupportedAddons.toTypedArray() + ) + navController.navigate(directions) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt b/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt index 92433aae5..f4128e335 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/Fragment.kt @@ -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) } diff --git a/app/src/main/java/org/mozilla/fenix/ext/NavController.kt b/app/src/main/java/org/mozilla/fenix/ext/NavController.kt index 912fd6e16..4034c2472 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/NavController.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/NavController.kt @@ -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 } diff --git a/app/src/test/java/org/mozilla/fenix/addons/AddonsManagementViewTest.kt b/app/src/test/java/org/mozilla/fenix/addons/AddonsManagementViewTest.kt new file mode 100644 index 000000000..901910ed0 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/addons/AddonsManagementViewTest.kt @@ -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 { + 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 { + 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() + managementView.onInstallAddonButtonClicked(addon) + verify { showPermissionDialog(addon) } + } + + @Test + fun `onNotYetSupportedSectionClicked shows not yet supported fragment`() { + val addons = listOf(mockk(), mockk()) + managementView.onNotYetSupportedSectionClicked(addons) + + val expected = AddonsManagementFragmentDirections.actionAddonsManagementFragmentToNotYetSupportedAddonFragment( + addons.toTypedArray() + ) + verify { + navController.navigate(directionsEq(expected)) + } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt index 462c8dd3e..d40da0e77 100644 --- a/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/DefaultBrowserToolbarControllerTest.kt @@ -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()) } + verify { + navController.navigate( + directionsEq(NavGraphDirections.actionGlobalShareFragment( + data = arrayOf(ShareData(url = "https://mozilla.org", title = "Mozilla")), + showPage = true + )) + ) + } } @Test diff --git a/app/src/test/java/org/mozilla/fenix/ext/FragmentTest.kt b/app/src/test/java/org/mozilla/fenix/ext/FragmentTest.kt index d9ffc99cb..5fd193f54 100644 --- a/app/src/test/java/org/mozilla/fenix/ext/FragmentTest.kt +++ b/app/src/test/java/org/mozilla/fenix/ext/FragmentTest.kt @@ -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 diff --git a/app/src/test/java/org/mozilla/fenix/ext/MockKMatcherScope.kt b/app/src/test/java/org/mozilla/fenix/ext/MockKMatcherScope.kt new file mode 100644 index 000000000..176a78b3c --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/ext/MockKMatcherScope.kt @@ -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 { + + override fun match(arg: NavDirections?): Boolean = + value.actionId == arg?.actionId && value.arguments contentEquals arg.arguments + + override fun substitute(map: Map) = + copy(value = value.internalSubstitute(map)) +} diff --git a/app/src/test/java/org/mozilla/fenix/ext/NavControllerTest.kt b/app/src/test/java/org/mozilla/fenix/ext/NavControllerTest.kt index 7e8931ad9..d459eec29 100644 --- a/app/src/test/java/org/mozilla/fenix/ext/NavControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/ext/NavControllerTest.kt @@ -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 diff --git a/app/src/test/java/org/mozilla/fenix/library/history/HistoryControllerTest.kt b/app/src/test/java/org/mozilla/fenix/library/history/HistoryControllerTest.kt index b3004e257..6fe7ddd29 100644 --- a/app/src/test/java/org/mozilla/fenix/library/history/HistoryControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/library/history/HistoryControllerTest.kt @@ -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() - 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).size) - assertEquals(historyItem.title, (directions.captured.arguments["data"] as Array)[0].title) - assertEquals(historyItem.url, (directions.captured.arguments["data"] as Array)[0].url) } @Test diff --git a/app/src/test/java/org/mozilla/fenix/settings/quicksettings/DefaultQuickSettingsControllerTest.kt b/app/src/test/java/org/mozilla/fenix/settings/quicksettings/DefaultQuickSettingsControllerTest.kt index 19e0d015d..4b0924c15 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/quicksettings/DefaultQuickSettingsControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/quicksettings/DefaultQuickSettingsControllerTest.kt @@ -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()) + navController.navigate(directionsEq( + QuickSettingsSheetDialogFragmentDirections.actionGlobalSitePermissionsManagePhoneFeature(PhoneFeature.CAMERA) + )) } }