2
0
mirror of https://github.com/fork-maintainers/iceraven-browser synced 2024-11-19 09:25:34 +00:00

Bug 1853299 - Part 3: Update review checker error states

This commit is contained in:
rahulsainani 2023-09-19 11:17:45 +02:00 committed by mergify[bot]
parent 5a390424c7
commit 43a6c7992b
9 changed files with 163 additions and 14 deletions

View File

@ -35,6 +35,7 @@ class ReviewQualityCheckFragment : BottomSheetDialogFragment() {
middleware = ReviewQualityCheckMiddlewareProvider.provideMiddleware(
settings = requireComponents.settings,
browserStore = requireComponents.core.store,
context = requireContext(),
openLink = { link, shouldOpenInNewTab ->
(requireActivity() as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = link,

View File

@ -4,8 +4,10 @@
package org.mozilla.fenix.shopping.di
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import mozilla.components.browser.state.store.BrowserStore
import org.mozilla.fenix.shopping.middleware.NetworkCheckerImpl
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckNavigationMiddleware
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckNetworkMiddleware
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckPreferencesImpl
@ -24,6 +26,7 @@ object ReviewQualityCheckMiddlewareProvider {
*
* @param settings The [Settings] instance to use.
* @param browserStore The [BrowserStore] instance to access state.
* @param context The [Context] instance to use.
* @param openLink Opens a link. The callback is invoked with the URL [String] parameter and
* whether or not it should open in a new or the currently selected tab [Boolean] parameter.
* @param scope The [CoroutineScope] to use for launching coroutines.
@ -31,12 +34,13 @@ object ReviewQualityCheckMiddlewareProvider {
fun provideMiddleware(
settings: Settings,
browserStore: BrowserStore,
context: Context,
openLink: (String, Boolean) -> Unit,
scope: CoroutineScope,
): List<ReviewQualityCheckMiddleware> =
listOf(
providePreferencesMiddleware(settings, scope),
provideNetworkMiddleware(browserStore, scope),
provideNetworkMiddleware(browserStore, context, scope),
provideNavigationMiddleware(openLink, scope),
)
@ -50,9 +54,11 @@ object ReviewQualityCheckMiddlewareProvider {
private fun provideNetworkMiddleware(
browserStore: BrowserStore,
context: Context,
scope: CoroutineScope,
) = ReviewQualityCheckNetworkMiddleware(
reviewQualityCheckService = ReviewQualityCheckServiceImpl(browserStore),
networkChecker = NetworkCheckerImpl(context),
scope = scope,
)

View File

@ -0,0 +1,33 @@
/* 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.shopping.middleware
import android.content.Context
import android.net.ConnectivityManager
import androidx.core.content.getSystemService
import org.mozilla.fenix.ext.isOnline
/**
* Checks if the device is connected to the internet.
*/
interface NetworkChecker {
/**
* @return true if the device is connected to the internet, false otherwise.
*/
fun isConnected(): Boolean
}
/**
* @see [NetworkChecker].
*/
class NetworkCheckerImpl(private val context: Context) : NetworkChecker {
private val connectivityManager by lazy { context.getSystemService<ConnectivityManager>() }
override fun isConnected(): Boolean {
return connectivityManager?.isOnline() ?: false
}
}

View File

@ -17,17 +17,17 @@ import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.OptedIn.ProductR
*/
fun ProductAnalysis?.toProductReviewState(): ProductReviewState =
if (this == null) {
ProductReviewState.Error
ProductReviewState.Error.GenericError
} else {
when (this) {
is GeckoProductAnalysis -> toProductReview()
else -> ProductReviewState.Error
else -> ProductReviewState.Error.GenericError
}
}
private fun GeckoProductAnalysis.toProductReview(): ProductReviewState =
if (productId == null) {
ProductReviewState.Error
ProductReviewState.NoAnalysisPresent()
} else {
val mappedRating = adjustedRating.toFloatOrNull()
val mappedGrade = grade?.toGrade()

View File

@ -13,15 +13,18 @@ import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction.FetchProductAnalysis
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction.RetryProductAnalysis
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.OptedIn.ProductReviewState
/**
* Middleware that handles network requests for the review quality check feature.
*
* @property reviewQualityCheckService The service that handles the network requests.
* @property networkChecker The [NetworkChecker] instance to check the network status.
* @property scope The [CoroutineScope] that will be used to launch coroutines.
*/
class ReviewQualityCheckNetworkMiddleware(
private val reviewQualityCheckService: ReviewQualityCheckService,
private val networkChecker: NetworkChecker,
private val scope: CoroutineScope,
) : Middleware<ReviewQualityCheckState, ReviewQualityCheckAction> {
@ -46,8 +49,11 @@ class ReviewQualityCheckNetworkMiddleware(
when (action) {
FetchProductAnalysis, RetryProductAnalysis -> {
scope.launch {
val analysis = reviewQualityCheckService.fetchProductReview()
val productReviewState = analysis.toProductReviewState()
val productReviewState = if (networkChecker.isConnected()) {
reviewQualityCheckService.fetchProductReview().toProductReviewState()
} else {
ProductReviewState.Error.NetworkError
}
store.dispatch(ReviewQualityCheckAction.UpdateProductReview(productReviewState))
}
}

View File

@ -49,7 +49,22 @@ sealed interface ReviewQualityCheckState : State {
/**
* Denotes an error has occurred.
*/
object Error : ProductReviewState
sealed interface Error : ProductReviewState {
/**
* Denotes a network error has occurred.
*/
object NetworkError : Error
/**
* Denotes a product is not supported.
*/
object UnsupportedProductTypeError : Error
/**
* Denotes a generic error has occurred.
*/
object GenericError : Error
}
/**
* Denotes no analysis is present for the product the user is browsing.
@ -92,6 +107,11 @@ sealed interface ReviewQualityCheckState : State {
highlights != null && showMoreButtonVisible &&
highlights.forCompactMode().entries.first().value.size > 1
val notEnoughReviewsCardVisible: Boolean =
(reviewGrade == null || adjustedRating == null) &&
analysisStatus != AnalysisStatus.NEEDS_ANALYSIS &&
analysisStatus != AnalysisStatus.REANALYZING
/**
* The status of the product analysis.
*/

View File

@ -107,16 +107,16 @@ class ProductAnalysisMapperTest {
@Test
fun `WHEN product analysis is null THEN it is mapped to Error`() {
val actual = null.toProductReviewState()
val expected = ReviewQualityCheckState.OptedIn.ProductReviewState.Error
val expected = ReviewQualityCheckState.OptedIn.ProductReviewState.Error.GenericError
assertEquals(expected, actual)
}
@Test
fun `WHEN product id is null THEN it is mapped to Error`() {
fun `WHEN product id is null THEN it is mapped to no analysis present`() {
val actual =
ProductAnalysisTestData.productAnalysis(productId = null).toProductReviewState()
val expected = ReviewQualityCheckState.OptedIn.ProductReviewState.Error
val expected = ReviewQualityCheckState.OptedIn.ProductReviewState.NoAnalysisPresent()
assertEquals(expected, actual)
}
@ -141,7 +141,7 @@ class ProductAnalysisMapperTest {
}
val actual = randomAnalysis.toProductReviewState()
val expected = ReviewQualityCheckState.OptedIn.ProductReviewState.Error
val expected = ReviewQualityCheckState.OptedIn.ProductReviewState.Error.GenericError
assertEquals(expected, actual)
}

View File

@ -10,6 +10,7 @@ import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Test
import org.mozilla.fenix.shopping.ProductAnalysisTestData
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.OptedIn.ProductReviewState.AnalysisPresent.AnalysisStatus
class ReviewQualityCheckStateTest {
@ -221,4 +222,51 @@ class ReviewQualityCheckStateTest {
assertTrue(analysis.showMoreButtonVisible)
assertFalse(analysis.highlightsFadeVisible)
}
@Test
fun `WHEN AnalysisPresent is created with grade or rating as null THEN not enough reviews card is visible`() {
val analysisWithoutGrade = ProductAnalysisTestData.analysisPresent(
reviewGrade = null,
adjustedRating = 3.2f,
analysisStatus = AnalysisStatus.COMPLETED,
)
val analysisWithoutRatings = ProductAnalysisTestData.analysisPresent(
reviewGrade = ReviewQualityCheckState.Grade.A,
adjustedRating = null,
analysisStatus = AnalysisStatus.UP_TO_DATE,
)
assertTrue(analysisWithoutGrade.notEnoughReviewsCardVisible)
assertTrue(analysisWithoutRatings.notEnoughReviewsCardVisible)
}
@Test
fun `WHEN AnalysisPresent is created with grade or rating as null and analysis status is needs analysis or reanalyzing THEN not enough reviews card is not visible`() {
val analysisWithoutGrade = ProductAnalysisTestData.analysisPresent(
reviewGrade = null,
adjustedRating = 3.2f,
analysisStatus = AnalysisStatus.NEEDS_ANALYSIS,
)
val analysisWithoutRatings = ProductAnalysisTestData.analysisPresent(
reviewGrade = ReviewQualityCheckState.Grade.A,
adjustedRating = null,
analysisStatus = AnalysisStatus.REANALYZING,
)
assertFalse(analysisWithoutGrade.notEnoughReviewsCardVisible)
assertFalse(analysisWithoutRatings.notEnoughReviewsCardVisible)
}
@Test
fun `WHEN AnalysisPresent is created with both grade and rating THEN not enough reviews card is not visible`() {
val analysis = ProductAnalysisTestData.analysisPresent(
reviewGrade = ReviewQualityCheckState.Grade.A,
adjustedRating = 3.2f,
analysisStatus = AnalysisStatus.UP_TO_DATE,
)
assertFalse(analysis.notEnoughReviewsCardVisible)
}
}

View File

@ -14,6 +14,7 @@ import mozilla.components.support.test.whenever
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.shopping.ProductAnalysisTestData
import org.mozilla.fenix.shopping.middleware.NetworkChecker
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckNetworkMiddleware
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckPreferences
import org.mozilla.fenix.shopping.middleware.ReviewQualityCheckPreferencesMiddleware
@ -167,6 +168,7 @@ class ReviewQualityCheckStoreTest {
middleware = provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(isEnabled = true),
reviewQualityCheckService = reviewQualityCheckService,
networkChecker = FakeNetworkChecker(isConnected = true),
),
)
tested.waitUntilIdle()
@ -184,7 +186,7 @@ class ReviewQualityCheckStoreTest {
}
@Test
fun `WHEN the user opts in the feature THEN update the preferences`() =
fun `GIVEN the user has opted in the feature WHEN the a product analysis returns an error THEN state should reflect that`() =
runTest {
val reviewQualityCheckService = mock<ReviewQualityCheckService>()
whenever(reviewQualityCheckService.fetchProductReview()).thenReturn(null)
@ -193,6 +195,7 @@ class ReviewQualityCheckStoreTest {
middleware = provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(isEnabled = true),
reviewQualityCheckService = reviewQualityCheckService,
networkChecker = FakeNetworkChecker(isConnected = true),
),
)
tested.waitUntilIdle()
@ -203,7 +206,31 @@ class ReviewQualityCheckStoreTest {
dispatcher.scheduler.advanceUntilIdle()
val expected = ReviewQualityCheckState.OptedIn(
productReviewState = ReviewQualityCheckState.OptedIn.ProductReviewState.Error,
productReviewState = ReviewQualityCheckState.OptedIn.ProductReviewState.Error.GenericError,
productRecommendationsPreference = false,
)
assertEquals(expected, tested.state)
}
@Test
fun `GIVEN the user has opted in the feature WHEN device is not connected to the internet THEN state should reflect that`() =
runTest {
val tested = ReviewQualityCheckStore(
middleware = provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(isEnabled = true),
reviewQualityCheckService = mock(),
networkChecker = FakeNetworkChecker(isConnected = false),
),
)
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.FetchProductAnalysis).joinBlocking()
tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle()
val expected = ReviewQualityCheckState.OptedIn(
productReviewState = ReviewQualityCheckState.OptedIn.ProductReviewState.Error.NetworkError,
productRecommendationsPreference = false,
)
assertEquals(expected, tested.state)
@ -212,8 +239,9 @@ class ReviewQualityCheckStoreTest {
private fun provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences: ReviewQualityCheckPreferences,
reviewQualityCheckService: ReviewQualityCheckService? = null,
networkChecker: NetworkChecker? = null,
): List<ReviewQualityCheckMiddleware> {
return if (reviewQualityCheckService != null) {
return if (reviewQualityCheckService != null && networkChecker != null) {
listOf(
ReviewQualityCheckPreferencesMiddleware(
reviewQualityCheckPreferences = reviewQualityCheckPreferences,
@ -221,6 +249,7 @@ class ReviewQualityCheckStoreTest {
),
ReviewQualityCheckNetworkMiddleware(
reviewQualityCheckService = reviewQualityCheckService,
networkChecker = networkChecker,
scope = this.scope,
),
)
@ -254,3 +283,9 @@ private class FakeReviewQualityCheckPreferences(
updateCFRCallback()
}
}
private class FakeNetworkChecker(
private val isConnected: Boolean,
) : NetworkChecker {
override fun isConnected(): Boolean = isConnected
}