From 4b064afb81f2e6578fb8f0d3721c345b99333095 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Wed, 10 Jun 2020 09:14:18 -0700 Subject: [PATCH] Add tests for ext package (#11334) --- .../org/mozilla/fenix/components/Analytics.kt | 2 +- .../java/org/mozilla/fenix/ext/Context.kt | 38 ------- .../main/java/org/mozilla/fenix/ext/Log.kt | 6 +- .../org/mozilla/fenix/ext/NavController.kt | 8 +- .../main/java/org/mozilla/fenix/ext/View.kt | 5 +- .../org/mozilla/fenix/ext/ImageButtonTest.kt | 7 ++ .../java/org/mozilla/fenix/ext/LogTest.kt | 41 +++++-- .../mozilla/fenix/ext/NavControllerTest.kt | 100 +++++++++++------- .../java/org/mozilla/fenix/ext/ViewTest.kt | 69 ++++++++++++ 9 files changed, 180 insertions(+), 96 deletions(-) create mode 100644 app/src/test/java/org/mozilla/fenix/ext/ViewTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt index cfbf468f4..12beefc36 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Analytics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Analytics.kt @@ -97,7 +97,7 @@ class Analytics( } } -private fun isSentryEnabled() = !BuildConfig.SENTRY_TOKEN.isNullOrEmpty() +fun isSentryEnabled() = !BuildConfig.SENTRY_TOKEN.isNullOrEmpty() private fun getSentryProjectUrl(): String? { val baseUrl = "https://sentry.prod.mozaws.net/operations" diff --git a/app/src/main/java/org/mozilla/fenix/ext/Context.kt b/app/src/main/java/org/mozilla/fenix/ext/Context.kt index 3c96b7330..a0447c63d 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/Context.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/Context.kt @@ -5,26 +5,17 @@ package org.mozilla.fenix.ext import android.app.Activity -import android.content.ActivityNotFoundException import android.content.Context -import android.content.Intent -import android.content.Intent.ACTION_SEND -import android.content.Intent.EXTRA_SUBJECT -import android.content.Intent.EXTRA_TEXT -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.view.ContextThemeWrapper import android.view.View import android.view.ViewGroup import androidx.annotation.StringRes import androidx.fragment.app.FragmentActivity import mozilla.components.browser.search.SearchEngineManager -import mozilla.components.support.base.log.Log -import mozilla.components.support.base.log.Log.Priority.WARN import mozilla.components.support.locale.LocaleManager import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.Config import org.mozilla.fenix.FenixApplication -import org.mozilla.fenix.R import org.mozilla.fenix.components.Components import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.settings.advanced.getSelectedLocale @@ -65,35 +56,6 @@ fun Context.asFragmentActivity() = (this as? ContextThemeWrapper)?.baseContext a fun Context.getPreferenceKey(@StringRes resourceId: Int): String = resources.getString(resourceId) -/** - * Shares content via [ACTION_SEND] intent. - * - * @param text the data to be shared [EXTRA_TEXT] - * @param subject of the intent [EXTRA_TEXT] - * @return true it is able to share false otherwise. - */ -@Deprecated("We are replacing the system share sheet with a custom version. See: [ShareFragment]") -fun Context.share(text: String, subject: String = ""): Boolean { - return try { - val intent = Intent(ACTION_SEND).apply { - type = "text/plain" - putExtra(EXTRA_SUBJECT, subject) - putExtra(EXTRA_TEXT, text) - flags = FLAG_ACTIVITY_NEW_TASK - } - - val shareIntent = Intent.createChooser(intent, getString(R.string.menu_share_with)).apply { - flags = FLAG_ACTIVITY_NEW_TASK - } - - startActivity(shareIntent) - true - } catch (e: ActivityNotFoundException) { - Log.log(WARN, message = "No activity to share to found", throwable = e, tag = "Reference-Browser") - false - } -} - /** * Gets the Root View with an activity context * diff --git a/app/src/main/java/org/mozilla/fenix/ext/Log.kt b/app/src/main/java/org/mozilla/fenix/ext/Log.kt index 63eca07ac..aa71fb5ba 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/Log.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/Log.kt @@ -2,6 +2,8 @@ * 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/. */ +@file:Suppress("NOTHING_TO_INLINE") + package org.mozilla.fenix.ext import android.util.Log @@ -12,7 +14,6 @@ import org.mozilla.fenix.Config * * Meant to be used for logs that should not be visible in the production app. */ -@Suppress("NOTHING_TO_INLINE") inline fun logDebug(tag: String, message: String) { if (Config.channel.isDebug) Log.d(tag, message) } @@ -22,7 +23,6 @@ inline fun logDebug(tag: String, message: String) { * * Meant to be used for logs that should not be visible in the production app. */ -@Suppress("NOTHING_TO_INLINE") inline fun logWarn(tag: String, message: String) { if (Config.channel.isDebug) Log.w(tag, message) } @@ -32,7 +32,6 @@ inline fun logWarn(tag: String, message: String) { * * Meant to be used for logs that should not be visible in the production app. */ -@Suppress("NOTHING_TO_INLINE") inline fun logWarn(tag: String, message: String, err: Throwable) { if (Config.channel.isDebug) Log.w(tag, message, err) } @@ -42,7 +41,6 @@ inline fun logWarn(tag: String, message: String, err: Throwable) { * * Meant to be used for logs that should not be visible in the production app. */ -@Suppress("NOTHING_TO_INLINE") inline fun logErr(tag: String, message: String, err: Throwable) { if (Config.channel.isDebug) Log.e(tag, message, err) } 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 4fae079f6..912fd6e16 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/NavController.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/NavController.kt @@ -11,8 +11,12 @@ import androidx.navigation.NavDirections import androidx.navigation.NavOptions import androidx.navigation.Navigator import io.sentry.Sentry -import org.mozilla.fenix.BuildConfig +import org.mozilla.fenix.components.isSentryEnabled +/** + * Navigate from the fragment with [id] using the given [directions]. + * If the id doesn't match the current destination, an error is recorded. + */ fun NavController.nav(@IdRes id: Int?, directions: NavDirections, navOptions: NavOptions? = null) { if (id == null || this.currentDestination?.id == id) { this.navigate(directions, navOptions) @@ -55,7 +59,7 @@ fun NavController.alreadyOnDestination(@IdRes destId: Int?): Boolean { } fun recordIdException(actual: Int?, expected: Int?) { - if (!BuildConfig.SENTRY_TOKEN.isNullOrEmpty()) { + if (isSentryEnabled()) { Sentry.capture("Fragment id $actual did not match expected $expected") } } diff --git a/app/src/main/java/org/mozilla/fenix/ext/View.kt b/app/src/main/java/org/mozilla/fenix/ext/View.kt index a58669a5b..3e414323c 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/View.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/View.kt @@ -15,10 +15,7 @@ fun View.increaseTapArea(extraDps: Int) { parent.post { val touchRect = Rect() getHitRect(touchRect) - touchRect.top -= dips - touchRect.left -= dips - touchRect.right += dips - touchRect.bottom += dips + touchRect.inset(-dips, -dips) parent.touchDelegate = TouchDelegate(touchRect, this) } } diff --git a/app/src/test/java/org/mozilla/fenix/ext/ImageButtonTest.kt b/app/src/test/java/org/mozilla/fenix/ext/ImageButtonTest.kt index e115551b6..71ee9605e 100644 --- a/app/src/test/java/org/mozilla/fenix/ext/ImageButtonTest.kt +++ b/app/src/test/java/org/mozilla/fenix/ext/ImageButtonTest.kt @@ -31,4 +31,11 @@ class ImageButtonTest { assertTrue(imageButton.isEnabled) assertEquals(View.VISIBLE, imageButton.visibility) } + + @Test + fun `Remove and disable`() { + imageButton.removeAndDisable() + assertFalse(imageButton.isEnabled) + assertEquals(View.GONE, imageButton.visibility) + } } diff --git a/app/src/test/java/org/mozilla/fenix/ext/LogTest.kt b/app/src/test/java/org/mozilla/fenix/ext/LogTest.kt index 9b9ae5212..201b75e72 100644 --- a/app/src/test/java/org/mozilla/fenix/ext/LogTest.kt +++ b/app/src/test/java/org/mozilla/fenix/ext/LogTest.kt @@ -5,44 +5,71 @@ package org.mozilla.fenix.ext import android.util.Log +import io.mockk.every import io.mockk.mockk +import io.mockk.mockkObject import io.mockk.mockkStatic import io.mockk.verify import org.junit.Before import org.junit.Test +import org.mozilla.fenix.Config +import org.mozilla.fenix.ReleaseChannel class LogTest { - private val numCalls = if (org.mozilla.fenix.Config.channel.isDebug) 1 else 0 + private val mockThrowable: Throwable = mockk() @Before fun setup() { mockkStatic(Log::class) + mockkObject(Config) + + every { Log.d(any(), any()) } returns 0 + every { Log.w(any(), any()) } returns 0 + every { Log.d(any(), any(), any()) } returns 0 + every { Log.d(any(), any(), any()) } returns 0 } @Test fun `Test log debug function`() { + every { Config.channel } returns ReleaseChannel.FenixDebug logDebug("hi", "hi") - verify(exactly = numCalls) { (Log.d("hi", "hi")) } + verify { Log.d("hi", "hi") } } @Test fun `Test log warn function with tag and message args`() { + every { Config.channel } returns ReleaseChannel.FenixDebug logWarn("hi", "hi") - verify(exactly = numCalls) { (Log.w("hi", "hi")) } + verify { Log.w("hi", "hi") } } @Test fun `Test log warn function with tag, message, and exception args`() { - val mockThrowable: Throwable = mockk(relaxed = true) + every { Config.channel } returns ReleaseChannel.FenixDebug logWarn("hi", "hi", mockThrowable) - verify(exactly = numCalls) { (Log.w("hi", "hi", mockThrowable)) } + verify { Log.w("hi", "hi", mockThrowable) } } @Test fun `Test log error function with tag, message, and exception args`() { - val mockThrowable: Throwable = mockk(relaxed = true) + every { Config.channel } returns ReleaseChannel.FenixDebug logErr("hi", "hi", mockThrowable) - verify(exactly = numCalls) { (Log.e("hi", "hi", mockThrowable)) } + verify { Log.e("hi", "hi", mockThrowable) } + } + + @Test + fun `Test no log in production channel`() { + every { Config.channel } returns ReleaseChannel.FenixProduction + + logDebug("hi", "hi") + logWarn("hi", "hi") + logWarn("hi", "hi", mockThrowable) + logErr("hi", "hi", mockThrowable) + + verify(exactly = 0) { Log.d(any(), any()) } + verify(exactly = 0) { Log.w(any(), any()) } + verify(exactly = 0) { Log.d(any(), any(), any()) } + verify(exactly = 0) { Log.d(any(), any(), any()) } } } 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 423469a04..7e8931ad9 100644 --- a/app/src/test/java/org/mozilla/fenix/ext/NavControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/ext/NavControllerTest.kt @@ -10,81 +10,101 @@ 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 import io.mockk.every -import io.mockk.mockk +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.mockkStatic import io.mockk.verify +import io.sentry.Sentry import org.junit.Before import org.junit.Test +import org.mozilla.fenix.components.isSentryEnabled class NavControllerTest { - private val navController: NavController = mockk(relaxed = true) - private val navDirections = mockk(relaxed = true) - private val mockDestination: NavDestination = mockk(relaxed = true) - private val mockExtras: Extras = mockk(relaxed = true) - private val mockOptions: NavOptions = mockk(relaxed = true) - private val mockBundle: Bundle = mockk(relaxed = true) + private val currentDestId = 4 + @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() { - every { (navController.currentDestination) } returns mockDestination - every { (mockDestination.id) } returns 4 + MockKAnnotations.init(this) + mockkStatic("io.sentry.Sentry", "org.mozilla.fenix.components.AnalyticsKt") + + every { navController.currentDestination } returns mockDestination + every { mockDestination.id } returns currentDestId + every { isSentryEnabled() } returns true + every { Sentry.capture(any()) } just Runs } @Test fun `Nav with id and directions args`() { - navController.nav(4, navDirections) - verify { (navController.currentDestination) } - verify { (navController.navigate(navDirections, null)) } + navController.nav(currentDestId, navDirections) + verify { navController.currentDestination } + verify { navController.navigate(navDirections, null) } } @Test fun `Nav with id, directions, and extras args`() { - navController.nav(4, navDirections, mockExtras) - verify { (navController.currentDestination) } - verify { (navController.navigate(navDirections, mockExtras)) } + navController.nav(currentDestId, navDirections, mockExtras) + verify { navController.currentDestination } + verify { navController.navigate(navDirections, mockExtras) } } @Test fun `Nav with id, directions, and options args`() { - navController.nav(4, navDirections, mockOptions) - verify { (navController.currentDestination) } - verify { (navController.navigate(navDirections, mockOptions)) } + navController.nav(currentDestId, navDirections, mockOptions) + verify { navController.currentDestination } + 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(4, 5, mockBundle, mockOptions, mockExtras) - verify { (navController.currentDestination) } - verify { (navController.navigate(5, mockBundle, mockOptions, mockExtras)) } + 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) - verify { (recordIdException(mockDestination.id, 7)) } + verify { navController.currentDestination } + verify { Sentry.capture("Fragment id 4 did not match expected 7") } + confirmVerified(navController) } - // TO-DO Not Working - /* @Test + @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`() { - mockkStatic(String::class) val actual = 7 - var expected = 4 + val expected = 4 - class MySentry() { - public fun capture(myString: String) { - println(myString) } - } - - class MyBuildConfig() { - public val SENTRY_TOKEN = "Mozilla" - } - - //val Sentry = MySentry() - val BuildConfig = MyBuildConfig() - every {BuildConfig.SENTRY_TOKEN.isNullOrEmpty()} returns false recordIdException(actual, expected) - //verify { Sentry.capture("Fragment id $actual did not match expected $expected")} - }*/ + verify { Sentry.capture("Fragment id 7 did not match expected 4") } + } } diff --git a/app/src/test/java/org/mozilla/fenix/ext/ViewTest.kt b/app/src/test/java/org/mozilla/fenix/ext/ViewTest.kt new file mode 100644 index 000000000..a70ec0abd --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/ext/ViewTest.kt @@ -0,0 +1,69 @@ +/* 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.ext + +import android.graphics.Rect +import android.util.DisplayMetrics +import android.view.View +import android.widget.FrameLayout +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.verify +import mozilla.components.support.ktx.android.util.dpToPx +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class ViewTest { + + @MockK private lateinit var view: View + @MockK private lateinit var parent: FrameLayout + @MockK private lateinit var displayMetrics: DisplayMetrics + + @Before + fun setup() { + MockKAnnotations.init(this) + mockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt") + + every { view.resources.displayMetrics } returns displayMetrics + every { view.parent } returns parent + every { parent.touchDelegate = any() } just Runs + every { parent.post(any()) } answers { + // Immediately run the given Runnable argument + val action: Runnable = firstArg() + action.run() + true + } + } + + @Test + fun `test increase touch area`() { + val hitRect = Rect(30, 40, 50, 60) + val dp = 10 + val px = 20 + val outRect = slot() + every { dp.dpToPx(displayMetrics) } returns px + every { view.getHitRect(capture(outRect)) } answers { outRect.captured.set(hitRect) } + + view.increaseTapArea(dp) + val expected = Rect(10, 20, 70, 80) + assertEquals(expected.left, outRect.captured.left) + assertEquals(expected.top, outRect.captured.top) + assertEquals(expected.right, outRect.captured.right) + assertEquals(expected.bottom, outRect.captured.bottom) + verify { parent.touchDelegate = any() } + } + + @Test + fun `test remove touch delegate`() { + view.removeTouchDelegate() + verify { parent.touchDelegate = null } + } +}