tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit a17801329eedb62102818a63454134616798911b
parent b92c6787a9e38ee63f27c2855b8187986df19ec3
Author: Marcin KoziƄski <mkozinski@mozilla.com>
Date:   Mon, 13 Oct 2025 11:30:18 +0000

Bug 1989529 - Add missing tests for PlayStoreReviewPromptController r=android-reviewers,gmalekpour

Differential Revision: https://phabricator.services.mozilla.com/D266150

Diffstat:
Amobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/Assertions.kt | 11+++++++++++
Mmobile/android/fenix/app/build.gradle | 1+
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/PlayStoreReviewPromptControllerTest.kt | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/reviewprompt/ReviewPromptMiddlewareTest.kt | 4+---
4 files changed, 111 insertions(+), 3 deletions(-)

diff --git a/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/Assertions.kt b/mobile/android/android-components/components/support/test/src/main/java/mozilla/components/support/test/Assertions.kt @@ -0,0 +1,11 @@ +/* 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 mozilla.components.support.test + +/** + * Fails the test if this function is used. + */ +fun assertUnused(): Nothing = + throw AssertionError("Expected unused function, but was called") diff --git a/mobile/android/fenix/app/build.gradle b/mobile/android/fenix/app/build.gradle @@ -707,6 +707,7 @@ dependencies { testImplementation testFixtures(project(':components:feature-downloads')) testImplementation ComponentsDependencies.mozilla_appservices_full_megazord_libsForTests + testImplementation libs.androidx.test.core testImplementation libs.androidx.test.junit testImplementation libs.androidx.work.testing testImplementation libs.kotlinx.coroutines.test diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/PlayStoreReviewPromptControllerTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/PlayStoreReviewPromptControllerTest.kt @@ -4,9 +4,25 @@ package org.mozilla.fenix.components +import android.app.Activity +import android.content.Context +import androidx.activity.ComponentActivity +import androidx.lifecycle.Lifecycle +import androidx.test.core.app.launchActivity +import com.google.android.gms.tasks.OnCompleteListener +import com.google.android.gms.tasks.OnFailureListener +import com.google.android.gms.tasks.OnSuccessListener +import com.google.android.gms.tasks.Task +import com.google.android.play.core.review.ReviewInfo +import com.google.android.play.core.review.ReviewManager +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import mozilla.components.support.test.assertUnused import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -16,6 +32,7 @@ import org.robolectric.RobolectricTestRunner import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import java.util.concurrent.Executor @RunWith(RobolectricTestRunner::class) class PlayStoreReviewPromptControllerTest { @@ -23,6 +40,43 @@ class PlayStoreReviewPromptControllerTest { @get:Rule val gleanTestRule = FenixGleanTestRule(testContext) + private val reviewManager = FakeReviewManager(testContext) + private val controller = PlayStoreReviewPromptController( + manager = reviewManager, + numberOfAppLaunches = { 5 }, + ) + + @Test + fun `GIVEN activity is resumed WHEN tryPromptReview is called THEN launches review flow`() = + runTest { + val scenario = launchActivity<ComponentActivity>() + scenario.moveToState(Lifecycle.State.RESUMED) + + scenario.onActivity { activity -> + launch { + controller.tryPromptReview(activity) + + assertTrue(reviewManager.promptHasBeenRequested) + } + } + } + + @Test + fun `GIVEN activity is stopped WHEN tryPromptReview is called THEN doesn't run the on complete callback`() = + runTest { + val scenario = launchActivity<ComponentActivity>() + scenario.moveToState(Lifecycle.State.RESUMED) + scenario.moveToState(Lifecycle.State.CREATED) + + scenario.onActivity { activity -> + launch { + controller.tryPromptReview(activity) + + assertFalse(reviewManager.promptHasBeenRequested) + } + } + } + @Test fun reviewPromptWasDisplayed() { testRecordReviewPromptEventRecordsTheExpectedData("isNoOp=false", "true") @@ -67,3 +121,47 @@ class PlayStoreReviewPromptControllerTest { } } } + +private class FakeReviewManager(context: Context) : ReviewManager { + var promptHasBeenRequested = false + + private val wrapped = com.google.android.play.core.review.testing.FakeReviewManager(context) + + override fun requestReviewFlow(): FakeGmsTask<ReviewInfo> { + val requestReviewFlow = wrapped.requestReviewFlow() + val result = requestReviewFlow.result + return FakeGmsTask(result) + } + + override fun launchReviewFlow(activity: Activity, reviewInfo: ReviewInfo): FakeGmsTask<Void?> { + promptHasBeenRequested = true + return FakeGmsTask(null) + } +} + +private class FakeGmsTask<T>(private val result: T) : Task<T>() { + override fun addOnCompleteListener(activity: Activity, listener: OnCompleteListener<T>): Task<T> { + val isNotStopped = (activity as ComponentActivity).lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) + if (isNotStopped) { + listener.onComplete(this) + } + return this + } + + override fun isSuccessful() = true + + override fun getResult() = result + + override fun <X : Throwable?> getResult(exceptionType: Class<X>) = result + + override fun isComplete() = assertUnused() + override fun isCanceled() = assertUnused() + override fun getException() = assertUnused() + override fun addOnSuccessListener(listener: OnSuccessListener<in T>) = assertUnused() + override fun addOnSuccessListener(executor: Executor, listener: OnSuccessListener<in T>) = assertUnused() + override fun addOnSuccessListener(activity: Activity, listener: OnSuccessListener<in T>) = assertUnused() + override fun addOnFailureListener(listener: OnFailureListener) = assertUnused() + override fun addOnFailureListener(executor: Executor, listener: OnFailureListener) = assertUnused() + override fun addOnFailureListener(activity: Activity, listener: OnFailureListener) = assertUnused() + override fun addOnCompleteListener(listener: OnCompleteListener<T>) = assertUnused() +} diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/reviewprompt/ReviewPromptMiddlewareTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/reviewprompt/ReviewPromptMiddlewareTest.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.reviewprompt +import mozilla.components.support.test.assertUnused import mozilla.components.support.test.ext.joinBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -290,9 +291,6 @@ class ReviewPromptMiddlewareTest { ) } - private fun assertUnused(): Nothing = - throw AssertionError("Expected unused function, but was called here ") - private class FakeNimbusMessagingHelperInterface(val evalJexlValue: Boolean) : NimbusMessagingHelperInterface { override fun evalJexl(expression: String): Boolean = evalJexlValue