Bug 1861694 - Use analysis status to restore (re)analysis state

(cherry picked from commit 860162c33c8519b80b65351d040989c88835cf6b)
fenix/121.0
rahulsainani 6 months ago committed by Ryan VanderMeulen
parent ca0b7e38c9
commit 14e7671ae3

@ -227,17 +227,6 @@ sealed class AppAction : Action {
*/ */
data class ShoppingSheetStateUpdated(val expanded: Boolean) : ShoppingAction() data class ShoppingSheetStateUpdated(val expanded: Boolean) : ShoppingAction()
/**
* [ShoppingAction] used to add a product to a set of products that are being analysed.
*/
data class AddToProductAnalysed(val productPageUrl: String) : ShoppingAction()
/**
* [ShoppingAction] used to remove a product from the set of products that are being
* analysed.
*/
data class RemoveFromProductAnalysed(val productPageUrl: String) : ShoppingAction()
/** /**
* [ShoppingAction] used to update the expansion state of the highlights card. * [ShoppingAction] used to update the expansion state of the highlights card.
*/ */

@ -7,14 +7,11 @@ package org.mozilla.fenix.components.appstate.shopping
/** /**
* State for shopping feature that's required to live the lifetime of a session. * State for shopping feature that's required to live the lifetime of a session.
* *
* @property productsInAnalysis Set of product urls that are currently being analysed or were being
* analysed when the sheet was closed.
* @property shoppingSheetExpanded Boolean indicating if the shopping sheet is expanded and visible. * @property shoppingSheetExpanded Boolean indicating if the shopping sheet is expanded and visible.
* @property productCardState Map of product url to [CardState] that contains the state of different * @property productCardState Map of product url to [CardState] that contains the state of different
* cards in the shopping sheet. * cards in the shopping sheet.
*/ */
data class ShoppingState( data class ShoppingState(
val productsInAnalysis: Set<String> = emptySet(),
val shoppingSheetExpanded: Boolean? = null, val shoppingSheetExpanded: Boolean? = null,
val productCardState: Map<String, CardState> = emptyMap(), val productCardState: Map<String, CardState> = emptyMap(),
) { ) {

@ -24,18 +24,6 @@ internal object ShoppingStateReducer {
), ),
) )
is ShoppingAction.AddToProductAnalysed -> state.copy(
shoppingState = state.shoppingState.copy(
productsInAnalysis = state.shoppingState.productsInAnalysis + action.productPageUrl,
),
)
is ShoppingAction.RemoveFromProductAnalysed -> state.copy(
shoppingState = state.shoppingState.copy(
productsInAnalysis = state.shoppingState.productsInAnalysis - action.productPageUrl,
),
)
is ShoppingAction.HighlightsCardExpanded -> { is ShoppingAction.HighlightsCardExpanded -> {
val updatedValue = val updatedValue =
state.shoppingState.productCardState[action.productPageUrl]?.copy( state.shoppingState.productCardState[action.productPageUrl]?.copy(

@ -45,9 +45,9 @@ object ReviewQualityCheckMiddlewareProvider {
): List<ReviewQualityCheckMiddleware> = ): List<ReviewQualityCheckMiddleware> =
listOf( listOf(
providePreferencesMiddleware(settings, browserStore, appStore, scope), providePreferencesMiddleware(settings, browserStore, appStore, scope),
provideNetworkMiddleware(browserStore, appStore, context, scope), provideNetworkMiddleware(browserStore, context, scope),
provideNavigationMiddleware(TabsUseCases.SelectOrAddUseCase(browserStore), context), provideNavigationMiddleware(TabsUseCases.SelectOrAddUseCase(browserStore), context),
provideTelemetryMiddleware(browserStore, appStore), provideTelemetryMiddleware(),
) )
private fun providePreferencesMiddleware( private fun providePreferencesMiddleware(
@ -65,13 +65,11 @@ object ReviewQualityCheckMiddlewareProvider {
private fun provideNetworkMiddleware( private fun provideNetworkMiddleware(
browserStore: BrowserStore, browserStore: BrowserStore,
appStore: AppStore,
context: Context, context: Context,
scope: CoroutineScope, scope: CoroutineScope,
) = ReviewQualityCheckNetworkMiddleware( ) = ReviewQualityCheckNetworkMiddleware(
reviewQualityCheckService = DefaultReviewQualityCheckService(browserStore), reviewQualityCheckService = DefaultReviewQualityCheckService(browserStore),
networkChecker = DefaultNetworkChecker(context), networkChecker = DefaultNetworkChecker(context),
appStore = appStore,
scope = scope, scope = scope,
) )
@ -83,11 +81,6 @@ object ReviewQualityCheckMiddlewareProvider {
GetReviewQualityCheckSumoUrl(context), GetReviewQualityCheckSumoUrl(context),
) )
private fun provideTelemetryMiddleware( private fun provideTelemetryMiddleware() =
browserStore: BrowserStore, ReviewQualityCheckTelemetryMiddleware()
appStore: AppStore,
) = ReviewQualityCheckTelemetryMiddleware(
browserStore = browserStore,
appStore = appStore,
)
} }

@ -6,11 +6,8 @@ package org.mozilla.fenix.shopping.middleware
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.concept.engine.shopping.ProductAnalysis
import mozilla.components.lib.state.MiddlewareContext import mozilla.components.lib.state.MiddlewareContext
import mozilla.components.lib.state.Store import mozilla.components.lib.state.Store
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction.ShoppingAction
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction.FetchProductAnalysis import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction.FetchProductAnalysis
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction.RetryProductAnalysis import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction.RetryProductAnalysis
@ -25,13 +22,11 @@ import org.mozilla.fenix.shopping.store.ReviewQualityCheckState.OptedIn.ProductR
* *
* @param reviewQualityCheckService The service that handles the network requests. * @param reviewQualityCheckService The service that handles the network requests.
* @param networkChecker The [NetworkChecker] instance to check the network status. * @param networkChecker The [NetworkChecker] instance to check the network status.
* @param appStore The [AppStore] instance to access state and dispatch [ShoppingAction]s.
* @param scope The [CoroutineScope] that will be used to launch coroutines. * @param scope The [CoroutineScope] that will be used to launch coroutines.
*/ */
class ReviewQualityCheckNetworkMiddleware( class ReviewQualityCheckNetworkMiddleware(
private val reviewQualityCheckService: ReviewQualityCheckService, private val reviewQualityCheckService: ReviewQualityCheckService,
private val networkChecker: NetworkChecker, private val networkChecker: NetworkChecker,
private val appStore: AppStore,
private val scope: CoroutineScope, private val scope: CoroutineScope,
) : ReviewQualityCheckMiddleware { ) : ReviewQualityCheckMiddleware {
@ -61,17 +56,19 @@ class ReviewQualityCheckNetworkMiddleware(
scope.launch { scope.launch {
when (action) { when (action) {
FetchProductAnalysis, RetryProductAnalysis -> { FetchProductAnalysis, RetryProductAnalysis -> {
val productPageUrl = reviewQualityCheckService.selectedTabUrl()
val productAnalysis = reviewQualityCheckService.fetchProductReview() val productAnalysis = reviewQualityCheckService.fetchProductReview()
val productReviewState = productAnalysis.toProductReviewState() val productReviewState = productAnalysis.toProductReviewState()
store.updateProductReviewState(productReviewState)
// Here the ProductReviewState should only updated after the analysis status API
productPageUrl?.let { // returns a result. This makes sure that the UI doesn't show the reanalyse
store.restoreAnalysingStateIfRequired( // button in case the product analysis is already in progress on the backend.
productPageUrl = productPageUrl, if (productReviewState.isAnalysisPresentOrNoAnalysisPresent() &&
productReviewState = productReviewState, reviewQualityCheckService.analysisStatus().isPendingOrInProgress()
productAnalysis = productAnalysis, ) {
) store.updateProductReviewState(productReviewState, true)
store.dispatch(ReviewQualityCheckAction.RestoreReanalysis)
} else {
store.updateProductReviewState(productReviewState)
} }
if (productReviewState is ProductReviewState.AnalysisPresent) { if (productReviewState is ProductReviewState.AnalysisPresent) {
@ -90,12 +87,6 @@ class ReviewQualityCheckNetworkMiddleware(
return@launch return@launch
} }
// add product to the set of products that are being analysed
val productPageUrl = reviewQualityCheckService.selectedTabUrl()
productPageUrl?.let {
appStore.dispatch(ShoppingAction.AddToProductAnalysed(it))
}
val status = pollForAnalysisStatus() val status = pollForAnalysisStatus()
if (status == null || if (status == null ||
@ -121,11 +112,6 @@ class ReviewQualityCheckNetworkMiddleware(
val productReviewState = productAnalysis.toProductReviewState() val productReviewState = productAnalysis.toProductReviewState()
store.updateProductReviewState(productReviewState) store.updateProductReviewState(productReviewState)
} }
// remove product from the set of products that are being analysed
productPageUrl?.let {
appStore.dispatch(ShoppingAction.RemoveFromProductAnalysed(it))
}
} }
is ReviewQualityCheckAction.RecommendedProductClick -> { is ReviewQualityCheckAction.RecommendedProductClick -> {
@ -141,27 +127,15 @@ class ReviewQualityCheckNetworkMiddleware(
private suspend fun pollForAnalysisStatus(): AnalysisStatusDto? = private suspend fun pollForAnalysisStatus(): AnalysisStatusDto? =
retry( retry(
predicate = { it == AnalysisStatusDto.PENDING || it == AnalysisStatusDto.IN_PROGRESS }, predicate = { it.isPendingOrInProgress() },
block = { reviewQualityCheckService.analysisStatus() }, block = { reviewQualityCheckService.analysisStatus() },
) )
private fun Store<ReviewQualityCheckState, ReviewQualityCheckAction>.updateProductReviewState( private fun Store<ReviewQualityCheckState, ReviewQualityCheckAction>.updateProductReviewState(
productReviewState: ProductReviewState, productReviewState: ProductReviewState,
restoreAnalysis: Boolean = false,
) { ) {
dispatch(ReviewQualityCheckAction.UpdateProductReview(productReviewState)) dispatch(ReviewQualityCheckAction.UpdateProductReview(productReviewState, restoreAnalysis))
}
private fun Store<ReviewQualityCheckState, ReviewQualityCheckAction>.restoreAnalysingStateIfRequired(
productPageUrl: String,
productReviewState: ProductReviewState,
productAnalysis: ProductAnalysis?,
) {
if (productReviewState.isAnalysisPresentOrNoAnalysisPresent() &&
productAnalysis?.needsAnalysis == true &&
appStore.state.shoppingState.productsInAnalysis.contains(productPageUrl)
) {
dispatch(ReviewQualityCheckAction.RestoreReanalysis)
}
} }
private fun ProductReviewState.isAnalysisPresentOrNoAnalysisPresent() = private fun ProductReviewState.isAnalysisPresentOrNoAnalysisPresent() =
@ -182,4 +156,7 @@ class ReviewQualityCheckNetworkMiddleware(
} }
} }
} }
private fun AnalysisStatusDto?.isPendingOrInProgress(): Boolean =
this == AnalysisStatusDto.PENDING || this == AnalysisStatusDto.IN_PROGRESS
} }

@ -41,11 +41,6 @@ interface ReviewQualityCheckService {
*/ */
suspend fun analysisStatus(): AnalysisStatusDto? suspend fun analysisStatus(): AnalysisStatusDto?
/**
* Returns the selected tab url.
*/
fun selectedTabUrl(): String?
/** /**
* Fetches product recommendations related to the product user is browsing in the current tab. * Fetches product recommendations related to the product user is browsing in the current tab.
* *
@ -126,9 +121,6 @@ class DefaultReviewQualityCheckService(
} }
} }
override fun selectedTabUrl(): String? =
browserStore.state.selectedTab?.content?.url
override suspend fun productRecommendation(shouldRecordAvailableTelemetry: Boolean): ProductRecommendation? = override suspend fun productRecommendation(shouldRecordAvailableTelemetry: Boolean): ProductRecommendation? =
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
suspendCoroutine { continuation -> suspendCoroutine { continuation ->

@ -4,13 +4,10 @@
package org.mozilla.fenix.shopping.middleware package org.mozilla.fenix.shopping.middleware
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.state.MiddlewareContext import mozilla.components.lib.state.MiddlewareContext
import mozilla.components.lib.state.Store import mozilla.components.lib.state.Store
import org.mozilla.fenix.GleanMetrics.Shopping import org.mozilla.fenix.GleanMetrics.Shopping
import org.mozilla.fenix.GleanMetrics.ShoppingSettings import org.mozilla.fenix.GleanMetrics.ShoppingSettings
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction import org.mozilla.fenix.shopping.store.ReviewQualityCheckAction
import org.mozilla.fenix.shopping.store.ReviewQualityCheckMiddleware import org.mozilla.fenix.shopping.store.ReviewQualityCheckMiddleware
import org.mozilla.fenix.shopping.store.ReviewQualityCheckState import org.mozilla.fenix.shopping.store.ReviewQualityCheckState
@ -22,10 +19,7 @@ private const val ACTION_DISABLED = "disabled"
/** /**
* Middleware that captures telemetry events for the review quality check feature. * Middleware that captures telemetry events for the review quality check feature.
*/ */
class ReviewQualityCheckTelemetryMiddleware( class ReviewQualityCheckTelemetryMiddleware : ReviewQualityCheckMiddleware {
private val browserStore: BrowserStore,
private val appStore: AppStore,
) : ReviewQualityCheckMiddleware {
override fun invoke( override fun invoke(
context: MiddlewareContext<ReviewQualityCheckState, ReviewQualityCheckAction>, context: MiddlewareContext<ReviewQualityCheckState, ReviewQualityCheckAction>,
@ -108,9 +102,8 @@ class ReviewQualityCheckTelemetryMiddleware(
} }
is ReviewQualityCheckAction.UpdateProductReview -> { is ReviewQualityCheckAction.UpdateProductReview -> {
val productPageUrl = browserStore.state.selectedTab?.content?.url
val state = store.state val state = store.state
if (state.isStaleAnalysis() && !isProductInAnalysis(productPageUrl)) { if (state.isStaleAnalysis() && !action.restoreAnalysis) {
Shopping.surfaceStaleAnalysisShown.record() Shopping.surfaceStaleAnalysisShown.record()
} }
} }
@ -167,9 +160,4 @@ class ReviewQualityCheckTelemetryMiddleware(
this is ReviewQualityCheckState.OptedIn && this is ReviewQualityCheckState.OptedIn &&
this.productReviewState is ReviewQualityCheckState.OptedIn.ProductReviewState.AnalysisPresent && this.productReviewState is ReviewQualityCheckState.OptedIn.ProductReviewState.AnalysisPresent &&
this.productReviewState.analysisStatus == AnalysisStatus.NEEDS_ANALYSIS this.productReviewState.analysisStatus == AnalysisStatus.NEEDS_ANALYSIS
private fun isProductInAnalysis(
productPageUrl: String?,
): Boolean =
appStore.state.shoppingState.productsInAnalysis.contains(productPageUrl)
} }

@ -89,8 +89,14 @@ sealed interface ReviewQualityCheckAction : Action {
/** /**
* Triggered as a result of a [NetworkAction] to update the [ProductReviewState]. * Triggered as a result of a [NetworkAction] to update the [ProductReviewState].
*
* @property productReviewState The product review state to update.
* @property restoreAnalysis Signals whether the analysis will be restored right after the update.
*/ */
data class UpdateProductReview(val productReviewState: ProductReviewState) : UpdateAction, TelemetryAction data class UpdateProductReview(
val productReviewState: ProductReviewState,
val restoreAnalysis: Boolean,
) : UpdateAction, TelemetryAction
/** /**
* Triggered as a result of a [NetworkAction] to update the [RecommendedProductState]. * Triggered as a result of a [NetworkAction] to update the [RecommendedProductState].

@ -47,38 +47,6 @@ class ShoppingActionTest {
assertEquals(expected, store.state.shoppingState) assertEquals(expected, store.state.shoppingState)
} }
@Test
fun `WHEN product analysed is added THEN state should reflect that`() {
val store = AppStore()
store.dispatch(AppAction.ShoppingAction.AddToProductAnalysed("pdp")).joinBlocking()
val expected = ShoppingState(
productsInAnalysis = setOf("pdp"),
)
assertEquals(expected, store.state.shoppingState)
}
@Test
fun `WHEN product analysed is removed THEN state should reflect that`() {
val store = AppStore(
initialState = AppState(
shoppingState = ShoppingState(
productsInAnalysis = setOf("pdp"),
),
),
)
store.dispatch(AppAction.ShoppingAction.RemoveFromProductAnalysed("pdp")).joinBlocking()
val expected = ShoppingState(
productsInAnalysis = emptySet(),
)
assertEquals(expected, store.state.shoppingState)
}
@Test @Test
fun `WHEN product analysis highlights card is expanded THEN state should reflect that`() { fun `WHEN product analysis highlights card is expanded THEN state should reflect that`() {
val store = AppStore(initialState = AppState(shoppingState = ShoppingState())) val store = AppStore(initialState = AppState(shoppingState = ShoppingState()))

@ -13,7 +13,6 @@ class FakeReviewQualityCheckService(
private val productAnalysis: (Int) -> ProductAnalysis? = { null }, private val productAnalysis: (Int) -> ProductAnalysis? = { null },
private val reanalysis: AnalysisStatusDto? = null, private val reanalysis: AnalysisStatusDto? = null,
private val status: AnalysisStatusDto? = null, private val status: AnalysisStatusDto? = null,
private val selectedTabUrl: String? = null,
private val productRecommendation: () -> ProductRecommendation? = { null }, private val productRecommendation: () -> ProductRecommendation? = { null },
private val recordClick: (String) -> Unit = {}, private val recordClick: (String) -> Unit = {},
private val recordImpression: (String) -> Unit = {}, private val recordImpression: (String) -> Unit = {},
@ -31,8 +30,6 @@ class FakeReviewQualityCheckService(
override suspend fun analysisStatus(): AnalysisStatusDto? = status override suspend fun analysisStatus(): AnalysisStatusDto? = status
override fun selectedTabUrl(): String? = selectedTabUrl
override suspend fun productRecommendation(shouldRecordAvailableTelemetry: Boolean): ProductRecommendation? { override suspend fun productRecommendation(shouldRecordAvailableTelemetry: Boolean): ProductRecommendation? {
return productRecommendation.invoke() return productRecommendation.invoke()
} }

@ -4,9 +4,6 @@
package org.mozilla.fenix.shopping.middleware package org.mozilla.fenix.shopping.middleware
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.service.glean.testing.GleanTestRule import mozilla.components.service.glean.testing.GleanTestRule
import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.libstate.ext.waitUntilIdle import mozilla.components.support.test.libstate.ext.waitUntilIdle
@ -19,8 +16,6 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.Shopping import org.mozilla.fenix.GleanMetrics.Shopping
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.shopping.ProductAnalysisTestData import org.mozilla.fenix.shopping.ProductAnalysisTestData
import org.mozilla.fenix.shopping.store.BottomSheetDismissSource import org.mozilla.fenix.shopping.store.BottomSheetDismissSource
@ -37,16 +32,12 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
val gleanTestRule = GleanTestRule(testContext) val gleanTestRule = GleanTestRule(testContext)
private lateinit var store: ReviewQualityCheckStore private lateinit var store: ReviewQualityCheckStore
private lateinit var browserStore: BrowserStore
private lateinit var appStore: AppStore
@Before @Before
fun setup() { fun setup() {
browserStore = BrowserStore()
appStore = AppStore()
store = ReviewQualityCheckStore( store = ReviewQualityCheckStore(
middleware = listOf( middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(browserStore, appStore), ReviewQualityCheckTelemetryMiddleware(),
), ),
) )
store.waitUntilIdle() store.waitUntilIdle()
@ -132,7 +123,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
isHighlightsExpanded = false, isHighlightsExpanded = false,
), ),
middleware = listOf( middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(browserStore, appStore), ReviewQualityCheckTelemetryMiddleware(),
), ),
) )
tested.waitUntilIdle() tested.waitUntilIdle()
@ -152,7 +143,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
isHighlightsExpanded = true, isHighlightsExpanded = true,
), ),
middleware = listOf( middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(browserStore, appStore), ReviewQualityCheckTelemetryMiddleware(),
), ),
) )
tested.waitUntilIdle() tested.waitUntilIdle()
@ -172,7 +163,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
isSettingsExpanded = false, isSettingsExpanded = false,
), ),
middleware = listOf( middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(browserStore, appStore), ReviewQualityCheckTelemetryMiddleware(),
), ),
) )
tested.waitUntilIdle() tested.waitUntilIdle()
@ -192,7 +183,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
isSettingsExpanded = true, isSettingsExpanded = true,
), ),
middleware = listOf( middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(browserStore, appStore), ReviewQualityCheckTelemetryMiddleware(),
), ),
) )
tested.waitUntilIdle() tested.waitUntilIdle()
@ -251,91 +242,76 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
} }
@Test @Test
fun `GIVEN a product review has been updated WHEN stale analysis is present and product is not in analysis THEN the stale analysis event is recorded`() { fun `GIVEN a product review has been updated WHEN restore analysis is false THEN the stale analysis event is recorded`() {
val productTab = createTab( val productReviewState = ProductAnalysisTestData.analysisPresent(
url = "pdp", analysisStatus = AnalysisPresent.AnalysisStatus.NEEDS_ANALYSIS,
)
val browserState = BrowserState(
tabs = listOf(productTab),
selectedTabId = productTab.id,
) )
val testedStore = ReviewQualityCheckStore(
val tested = ReviewQualityCheckStore(
initialState = ReviewQualityCheckState.OptedIn( initialState = ReviewQualityCheckState.OptedIn(
productReviewState = ProductAnalysisTestData.analysisPresent( productRecommendationsPreference = null,
analysisStatus = AnalysisPresent.AnalysisStatus.UP_TO_DATE,
),
productRecommendationsPreference = false,
productRecommendationsExposure = true, productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON, productVendor = ReviewQualityCheckState.ProductVendor.BEST_BUY,
),
middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(BrowserStore(browserState), appStore),
), ),
) middleware = listOf(ReviewQualityCheckTelemetryMiddleware()),
val productReviewState = ProductAnalysisTestData.analysisPresent(
analysisStatus = AnalysisPresent.AnalysisStatus.NEEDS_ANALYSIS,
) )
testedStore.dispatch(ReviewQualityCheckAction.UpdateProductReview(productReviewState)).joinBlocking() tested.dispatch(
testedStore.waitUntilIdle() ReviewQualityCheckAction.UpdateProductReview(
productReviewState = productReviewState,
restoreAnalysis = false,
),
).joinBlocking()
tested.waitUntilIdle()
assertNotNull(Shopping.surfaceStaleAnalysisShown.testGetValue()) assertNotNull(Shopping.surfaceStaleAnalysisShown.testGetValue())
} }
@Test @Test
fun `GIVEN a product review has been updated WHEN stale analysis is present and product is being analyzed THEN the stale analysis event is not recorded`() { fun `GIVEN a product review has been updated WHEN restore analysis is true THEN the stale analysis event is not recorded`() {
val productTab = createTab(
url = "pdp",
)
appStore.dispatch(AppAction.ShoppingAction.AddToProductAnalysed("pdp")).joinBlocking()
appStore.waitUntilIdle()
val browserState = BrowserState(
tabs = listOf(productTab),
selectedTabId = productTab.id,
)
val testedStore = ReviewQualityCheckStore(
middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(BrowserStore(browserState), appStore),
),
)
val productReviewState = ProductAnalysisTestData.analysisPresent( val productReviewState = ProductAnalysisTestData.analysisPresent(
analysisStatus = AnalysisPresent.AnalysisStatus.NEEDS_ANALYSIS, analysisStatus = AnalysisPresent.AnalysisStatus.NEEDS_ANALYSIS,
) )
val tested = ReviewQualityCheckStore(
initialState = ReviewQualityCheckState.OptedIn(
productRecommendationsPreference = null,
productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.BEST_BUY,
),
middleware = listOf(ReviewQualityCheckTelemetryMiddleware()),
)
testedStore.dispatch(ReviewQualityCheckAction.UpdateProductReview(productReviewState)).joinBlocking() tested.dispatch(
testedStore.waitUntilIdle() ReviewQualityCheckAction.UpdateProductReview(
productReviewState = productReviewState,
restoreAnalysis = true,
),
).joinBlocking()
tested.waitUntilIdle()
assertNull(Shopping.surfaceStaleAnalysisShown.testGetValue()) assertNull(Shopping.surfaceStaleAnalysisShown.testGetValue())
} }
@Test @Test
fun `GIVEN a product review has been updated WHEN it is not a stale analysis THEN the stale analysis event is not recorded`() { fun `GIVEN a product review has been updated WHEN it is not a stale analysis THEN the stale analysis event is not recorded`() {
val productTab = createTab( val productReviewState = ProductAnalysisTestData.analysisPresent()
url = "pdp",
) val tested = ReviewQualityCheckStore(
val browserState = BrowserState(
tabs = listOf(productTab),
selectedTabId = productTab.id,
)
val testedStore = ReviewQualityCheckStore(
initialState = ReviewQualityCheckState.OptedIn( initialState = ReviewQualityCheckState.OptedIn(
productReviewState = ProductAnalysisTestData.analysisPresent( productRecommendationsPreference = null,
analysisStatus = AnalysisPresent.AnalysisStatus.NEEDS_ANALYSIS,
),
productRecommendationsPreference = false,
productRecommendationsExposure = true, productRecommendationsExposure = true,
productVendor = ReviewQualityCheckState.ProductVendor.AMAZON, productVendor = ReviewQualityCheckState.ProductVendor.BEST_BUY,
),
middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(BrowserStore(browserState), appStore),
), ),
) middleware = listOf(ReviewQualityCheckTelemetryMiddleware()),
val productReviewState = ProductAnalysisTestData.analysisPresent(
analysisStatus = AnalysisPresent.AnalysisStatus.REANALYZING,
) )
testedStore.dispatch(ReviewQualityCheckAction.UpdateProductReview(productReviewState)).joinBlocking() tested.dispatch(
testedStore.waitUntilIdle() ReviewQualityCheckAction.UpdateProductReview(
productReviewState = productReviewState,
restoreAnalysis = true,
),
).joinBlocking()
tested.waitUntilIdle()
assertNull(Shopping.surfaceStaleAnalysisShown.testGetValue()) assertNull(Shopping.surfaceStaleAnalysisShown.testGetValue())
} }
@ -366,7 +342,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
isHighlightsExpanded = false, isHighlightsExpanded = false,
), ),
middleware = listOf( middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(browserStore, appStore), ReviewQualityCheckTelemetryMiddleware(),
), ),
) )
tested.waitUntilIdle() tested.waitUntilIdle()
@ -386,7 +362,7 @@ class ReviewQualityCheckTelemetryMiddlewareTest {
isHighlightsExpanded = false, isHighlightsExpanded = false,
), ),
middleware = listOf( middleware = listOf(
ReviewQualityCheckTelemetryMiddleware(browserStore, appStore), ReviewQualityCheckTelemetryMiddleware(),
), ),
) )
tested.waitUntilIdle() tested.waitUntilIdle()

@ -717,23 +717,19 @@ class ReviewQualityCheckStoreTest {
} }
@Test @Test
fun `GIVEN that the product was being analysed earlier WHEN needsAnalysis is true THEN state should be restored to reanalysing`() = fun `GIVEN a product analysis WHEN analysis status is in progress or pending THEN state should be updated to reanalysing`() =
runTest { runTest {
val tested = ReviewQualityCheckStore( val tested = ReviewQualityCheckStore(
middleware = provideReviewQualityCheckMiddleware( middleware = provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(isEnabled = true), reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(isEnabled = true),
reviewQualityCheckService = FakeReviewQualityCheckService( reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { productAnalysis = {
ProductAnalysisTestData.productAnalysis(needsAnalysis = true) ProductAnalysisTestData.productAnalysis()
}, },
reanalysis = AnalysisStatusDto.PENDING, reanalysis = AnalysisStatusDto.PENDING,
status = AnalysisStatusDto.COMPLETED, status = AnalysisStatusDto.IN_PROGRESS,
selectedTabUrl = "pdp",
), ),
networkChecker = FakeNetworkChecker(isConnected = true), networkChecker = FakeNetworkChecker(isConnected = true),
appStore = AppStore(
AppState(shoppingState = ShoppingState(productsInAnalysis = setOf("pdp"))),
),
), ),
) )
@ -764,7 +760,7 @@ class ReviewQualityCheckStoreTest {
} }
@Test @Test
fun `GIVEN that the product was not being analysed earlier WHEN needsAnalysis is true THEN state should display needs analysis as usual`() = fun `GIVEN a product analysis WHEN analysis status is completed THEN state should display analysis as usual`() =
runTest { runTest {
val tested = ReviewQualityCheckStore( val tested = ReviewQualityCheckStore(
middleware = provideReviewQualityCheckMiddleware( middleware = provideReviewQualityCheckMiddleware(
@ -773,18 +769,10 @@ class ReviewQualityCheckStoreTest {
productAnalysis = { productAnalysis = {
ProductAnalysisTestData.productAnalysis(needsAnalysis = true) ProductAnalysisTestData.productAnalysis(needsAnalysis = true)
}, },
reanalysis = AnalysisStatusDto.PENDING, reanalysis = AnalysisStatusDto.COMPLETED,
status = AnalysisStatusDto.COMPLETED, status = AnalysisStatusDto.COMPLETED,
selectedTabUrl = "pdp",
), ),
networkChecker = FakeNetworkChecker(isConnected = true), networkChecker = FakeNetworkChecker(isConnected = true),
appStore = AppStore(
AppState(
shoppingState = ShoppingState(
productsInAnalysis = setOf("test", "another", "product"),
),
),
),
), ),
) )
@ -822,34 +810,51 @@ class ReviewQualityCheckStoreTest {
} }
@Test @Test
fun `WHEN reanalysis is triggered THEN shopping state should contain the url of the product being analyzed`() = fun `GIVEN a product analysis WHEN analysis status fails THEN state should display analysis as usual`() =
runTest { runTest {
val captureActionsMiddleware = CaptureActionsMiddleware<AppState, AppAction>()
val appStore = AppStore(middlewares = listOf(captureActionsMiddleware))
val tested = ReviewQualityCheckStore( val tested = ReviewQualityCheckStore(
middleware = provideReviewQualityCheckMiddleware( middleware = provideReviewQualityCheckMiddleware(
reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(isEnabled = true), reviewQualityCheckPreferences = FakeReviewQualityCheckPreferences(isEnabled = true),
reviewQualityCheckService = FakeReviewQualityCheckService( reviewQualityCheckService = FakeReviewQualityCheckService(
productAnalysis = { ProductAnalysisTestData.productAnalysis() }, productAnalysis = {
reanalysis = AnalysisStatusDto.PENDING, ProductAnalysisTestData.productAnalysis()
status = AnalysisStatusDto.COMPLETED, },
selectedTabUrl = "pdp", reanalysis = null,
status = null,
), ),
networkChecker = FakeNetworkChecker(isConnected = true), networkChecker = FakeNetworkChecker(isConnected = true),
appStore = appStore,
), ),
) )
val observedState = mutableListOf<ReviewQualityCheckState>()
tested.observeForever {
observedState.add(it)
}
tested.waitUntilIdle() tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle() dispatcher.scheduler.advanceUntilIdle()
tested.waitUntilIdle() tested.waitUntilIdle()
tested.dispatch(ReviewQualityCheckAction.ReanalyzeProduct).joinBlocking() tested.dispatch(ReviewQualityCheckAction.FetchProductAnalysis).joinBlocking()
tested.waitUntilIdle() tested.waitUntilIdle()
dispatcher.scheduler.advanceUntilIdle() dispatcher.scheduler.advanceUntilIdle()
captureActionsMiddleware.assertFirstAction(AppAction.ShoppingAction.AddToProductAnalysed::class) { val expected = ReviewQualityCheckState.OptedIn(
assertEquals("pdp", it.productPageUrl) productReviewState = ProductAnalysisTestData.analysisPresent(),
} productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertEquals(expected, tested.state)
val notExpected = ReviewQualityCheckState.OptedIn(
productReviewState = ProductAnalysisTestData.analysisPresent(
analysisStatus = AnalysisStatus.REANALYZING,
),
productRecommendationsPreference = false,
productRecommendationsExposure = true,
productVendor = ProductVendor.BEST_BUY,
)
assertFalse(observedState.contains(notExpected))
} }
@Test @Test
@ -1166,7 +1171,6 @@ class ReviewQualityCheckStoreTest {
ReviewQualityCheckNetworkMiddleware( ReviewQualityCheckNetworkMiddleware(
reviewQualityCheckService = reviewQualityCheckService, reviewQualityCheckService = reviewQualityCheckService,
networkChecker = networkChecker, networkChecker = networkChecker,
appStore = appStore,
scope = this.scope, scope = this.scope,
), ),
) )

Loading…
Cancel
Save