diff --git a/purchases/src/main/kotlin/com/revenuecat/purchases/paywalls/components/PackageComponent.kt b/purchases/src/main/kotlin/com/revenuecat/purchases/paywalls/components/PackageComponent.kt index 9370688fe2..2270d0aa45 100644 --- a/purchases/src/main/kotlin/com/revenuecat/purchases/paywalls/components/PackageComponent.kt +++ b/purchases/src/main/kotlin/com/revenuecat/purchases/paywalls/components/PackageComponent.kt @@ -2,6 +2,7 @@ package com.revenuecat.purchases.paywalls.components import androidx.compose.runtime.Immutable import com.revenuecat.purchases.InternalRevenueCatAPI +import com.revenuecat.purchases.paywalls.components.common.ComponentOverride import com.revenuecat.purchases.paywalls.components.common.PromoOfferConfig import com.revenuecat.purchases.paywalls.components.common.ResilientPromoOfferConfigSerializer import dev.drewhamilton.poko.Poko @@ -26,4 +27,17 @@ public class PackageComponent( @Serializable(with = ResilientPromoOfferConfigSerializer::class) @SerialName("play_store_offer") public val playStoreOffer: PromoOfferConfig? = null, + @get:JvmSynthetic + public val visible: Boolean? = null, + @get:JvmSynthetic + public val overrides: List> = emptyList(), ) : PaywallComponent + +@InternalRevenueCatAPI +@Poko +@Serializable +@Immutable +public class PartialPackageComponent( + @get:JvmSynthetic + public val visible: Boolean? = null, +) : PartialComponent diff --git a/purchases/src/test/java/com/revenuecat/purchases/paywalls/components/PackageComponentTests.kt b/purchases/src/test/java/com/revenuecat/purchases/paywalls/components/PackageComponentTests.kt index 7ec845ec10..9a43f8a30a 100644 --- a/purchases/src/test/java/com/revenuecat/purchases/paywalls/components/PackageComponentTests.kt +++ b/purchases/src/test/java/com/revenuecat/purchases/paywalls/components/PackageComponentTests.kt @@ -4,6 +4,7 @@ import com.revenuecat.purchases.ColorAlias import com.revenuecat.purchases.JsonTools import com.revenuecat.purchases.LogHandler import com.revenuecat.purchases.common.currentLogHandler +import com.revenuecat.purchases.paywalls.components.common.ComponentOverride import com.revenuecat.purchases.paywalls.components.common.LocalizationKey import com.revenuecat.purchases.paywalls.components.common.PromoOfferConfig import com.revenuecat.purchases.paywalls.components.properties.ColorInfo @@ -187,6 +188,87 @@ internal class PackageComponentTests(@Suppress("UNUSED_PARAMETER") name: String, ) ), ), + arrayOf( + "visible = true", + Args( + json = """ + { + "type": "package", + "package_id": "${"$"}rc_weekly", + "is_selected_by_default": true, + "visible": true, + "stack": { + "type": "stack", + "components": [] + } + } + """.trimIndent(), + expected = PackageComponent( + packageId = "${"$"}rc_weekly", + isSelectedByDefault = true, + stack = StackComponent(components = emptyList()), + visible = true, + ) + ), + ), + arrayOf( + "visible = false", + Args( + json = """ + { + "type": "package", + "package_id": "${"$"}rc_weekly", + "is_selected_by_default": true, + "visible": false, + "stack": { + "type": "stack", + "components": [] + } + } + """.trimIndent(), + expected = PackageComponent( + packageId = "${"$"}rc_weekly", + isSelectedByDefault = true, + stack = StackComponent(components = emptyList()), + visible = false, + ) + ), + ), + arrayOf( + "overrides with visible = false", + Args( + json = """ + { + "type": "package", + "package_id": "${"$"}rc_weekly", + "is_selected_by_default": true, + "stack": { + "type": "stack", + "components": [] + }, + "overrides": [ + { + "conditions": [{"type": "intro_offer"}], + "properties": {"visible": false} + } + ] + } + """.trimIndent(), + expected = PackageComponent( + packageId = "${"$"}rc_weekly", + isSelectedByDefault = true, + stack = StackComponent(components = emptyList()), + overrides = listOf( + ComponentOverride( + conditions = listOf( + ComponentOverride.Condition.IntroOffer + ), + properties = PartialPackageComponent(visible = false), + ) + ), + ) + ), + ), ) } diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/PresentedPackagePartial.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/PresentedPackagePartial.kt new file mode 100644 index 0000000000..e9486c6af7 --- /dev/null +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/PresentedPackagePartial.kt @@ -0,0 +1,30 @@ +package com.revenuecat.purchases.ui.revenuecatui.components + +import com.revenuecat.purchases.paywalls.components.PartialPackageComponent +import com.revenuecat.purchases.ui.revenuecatui.errors.PaywallValidationError +import com.revenuecat.purchases.ui.revenuecatui.helpers.NonEmptyList +import com.revenuecat.purchases.ui.revenuecatui.helpers.Result +import dev.drewhamilton.poko.Poko + +@Poko +internal class PresentedPackagePartial( + @get:JvmSynthetic val partial: PartialPackageComponent, +) : PresentedPartial { + + companion object { + @JvmSynthetic + operator fun invoke( + from: PartialPackageComponent, + ): Result> = + Result.Success(PresentedPackagePartial(partial = from)) + } + + override fun combine(with: PresentedPackagePartial?): PresentedPackagePartial { + val otherPartial = with?.partial + return PresentedPackagePartial( + partial = PartialPackageComponent( + visible = otherPartial?.visible ?: partial.visible, + ), + ) + } +} diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/pkg/PackageComponentState.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/pkg/PackageComponentState.kt new file mode 100644 index 0000000000..082386820a --- /dev/null +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/pkg/PackageComponentState.kt @@ -0,0 +1,107 @@ +@file:JvmSynthetic + +package com.revenuecat.purchases.ui.revenuecatui.components.pkg + +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.window.core.layout.WindowWidthSizeClass +import com.revenuecat.purchases.ui.revenuecatui.CustomVariableValue +import com.revenuecat.purchases.ui.revenuecatui.components.ComponentViewState +import com.revenuecat.purchases.ui.revenuecatui.components.ConditionContext +import com.revenuecat.purchases.ui.revenuecatui.components.ScreenCondition +import com.revenuecat.purchases.ui.revenuecatui.components.buildPresentedPartial +import com.revenuecat.purchases.ui.revenuecatui.components.state.PackageAwareDelegate +import com.revenuecat.purchases.ui.revenuecatui.components.style.PackageComponentStyle +import com.revenuecat.purchases.ui.revenuecatui.composables.OfferEligibility +import com.revenuecat.purchases.ui.revenuecatui.data.PaywallState + +@Stable +@JvmSynthetic +@Composable +internal fun rememberUpdatedPackageComponentState( + style: PackageComponentStyle, + paywallState: PaywallState.Loaded.Components, +): PackageComponentState = rememberUpdatedPackageComponentState( + style = style, + selectedPackageInfoProvider = { paywallState.selectedPackageInfo }, + selectedTabIndexProvider = { paywallState.selectedTabIndex }, + selectedOfferEligibilityProvider = { paywallState.selectedOfferEligibility }, + customVariablesProvider = { paywallState.mergedCustomVariables }, +) + +@Suppress("LongParameterList") +@Stable +@JvmSynthetic +@Composable +private fun rememberUpdatedPackageComponentState( + style: PackageComponentStyle, + selectedPackageInfoProvider: () -> PaywallState.Loaded.Components.SelectedPackageInfo?, + selectedTabIndexProvider: () -> Int, + selectedOfferEligibilityProvider: () -> OfferEligibility, + customVariablesProvider: () -> Map, +): PackageComponentState { + val windowSize = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass + + return remember(style) { + PackageComponentState( + initialWindowSize = windowSize, + style = style, + selectedPackageInfoProvider = selectedPackageInfoProvider, + selectedTabIndexProvider = selectedTabIndexProvider, + selectedOfferEligibilityProvider = selectedOfferEligibilityProvider, + customVariablesProvider = customVariablesProvider, + ) + }.apply { + update(windowSize = windowSize) + } +} + +@Suppress("LongParameterList") +@Stable +internal class PackageComponentState( + initialWindowSize: WindowWidthSizeClass, + private val style: PackageComponentStyle, + private val selectedPackageInfoProvider: () -> PaywallState.Loaded.Components.SelectedPackageInfo?, + private val selectedTabIndexProvider: () -> Int, + private val selectedOfferEligibilityProvider: () -> OfferEligibility, + private val customVariablesProvider: () -> Map = { emptyMap() }, +) { + private var windowSize by mutableStateOf(initialWindowSize) + + private val packageAwareDelegate = PackageAwareDelegate( + style = style, + selectedPackageInfoProvider = selectedPackageInfoProvider, + selectedTabIndexProvider = selectedTabIndexProvider, + selectedOfferEligibilityProvider = selectedOfferEligibilityProvider, + ) + + private val presentedPartial by derivedStateOf { + val windowCondition = ScreenCondition.from(windowSize) + val componentState = + if (packageAwareDelegate.isSelected) ComponentViewState.SELECTED else ComponentViewState.DEFAULT + + style.overrides.buildPresentedPartial( + windowCondition, + packageAwareDelegate.offerEligibility, + componentState, + conditionContext = ConditionContext( + selectedPackageId = selectedPackageInfoProvider()?.rcPackage?.identifier, + customVariables = customVariablesProvider(), + ), + ) + } + + @get:JvmSynthetic + val visible by derivedStateOf { presentedPartial?.partial?.visible ?: style.visible } + + @JvmSynthetic + fun update(windowSize: WindowWidthSizeClass) { + this.windowSize = windowSize + } +} diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/pkg/PackageComponentView.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/pkg/PackageComponentView.kt index a30ae5de5b..9ccb4753f3 100644 --- a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/pkg/PackageComponentView.kt +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/pkg/PackageComponentView.kt @@ -19,6 +19,10 @@ internal fun PackageComponentView( clickHandler: suspend (PaywallAction) -> Unit, modifier: Modifier = Modifier, ) { + val packageState = rememberUpdatedPackageComponentState(style = style, paywallState = state) + + if (!packageState.visible) return + StackComponentView( style = style.stackComponentStyle, state = state, diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/PackageComponentStyle.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/PackageComponentStyle.kt index 89eb447278..be6b11e85f 100644 --- a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/PackageComponentStyle.kt +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/PackageComponentStyle.kt @@ -3,28 +3,41 @@ package com.revenuecat.purchases.ui.revenuecatui.components.style import androidx.compose.runtime.Immutable import com.revenuecat.purchases.Package import com.revenuecat.purchases.paywalls.components.properties.Size +import com.revenuecat.purchases.ui.revenuecatui.components.PresentedOverride +import com.revenuecat.purchases.ui.revenuecatui.components.PresentedPackagePartial +import com.revenuecat.purchases.ui.revenuecatui.components.state.PackageContext +import com.revenuecat.purchases.ui.revenuecatui.composables.OfferEligibility import com.revenuecat.purchases.ui.revenuecatui.helpers.ResolvedOffer +@Suppress("LongParameterList") @Immutable internal data class PackageComponentStyle( @get:JvmSynthetic - val rcPackage: Package, + override val rcPackage: Package, @get:JvmSynthetic val isSelectedByDefault: Boolean, @get:JvmSynthetic val stackComponentStyle: StackComponentStyle, @get:JvmSynthetic val isSelectable: Boolean, + @get:JvmSynthetic + override val resolvedOffer: ResolvedOffer? = null, + @get:JvmSynthetic + override val visible: Boolean, + @get:JvmSynthetic + val overrides: List>, /** - * The resolved Play Store offer for this package, if configured. - * Used for purchase flow and template variables. + * Pre-computed offer eligibility for this package, used for evaluating intro/promo offer conditions + * in package-level overrides. */ @get:JvmSynthetic - val resolvedOffer: ResolvedOffer? = null, -) : ComponentStyle { - override val visible: Boolean = stackComponentStyle.visible + override val offerEligibility: OfferEligibility? = null, +) : ComponentStyle, PackageContext { override val size: Size = stackComponentStyle.size + @get:JvmSynthetic + override val tabIndex: Int? = null + /** * Unique identifier for this package component, combining package ID and offer ID. * This allows distinguishing between multiple components that reference the same package diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactory.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactory.kt index 4ce24e5da7..fdcd4c3d6a 100644 --- a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactory.kt +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactory.kt @@ -39,6 +39,7 @@ import com.revenuecat.purchases.ui.revenuecatui.components.LocalizedTextPartial import com.revenuecat.purchases.ui.revenuecatui.components.PresentedCarouselPartial import com.revenuecat.purchases.ui.revenuecatui.components.PresentedIconPartial import com.revenuecat.purchases.ui.revenuecatui.components.PresentedImagePartial +import com.revenuecat.purchases.ui.revenuecatui.components.PresentedPackagePartial import com.revenuecat.purchases.ui.revenuecatui.components.PresentedStackPartial import com.revenuecat.purchases.ui.revenuecatui.components.PresentedTabsPartial import com.revenuecat.purchases.ui.revenuecatui.components.PresentedTimelineItemPartial @@ -573,19 +574,33 @@ internal class StyleFactory( // visually become "selected" if its tab control parent is. tabControlIndex = null, ) { + val packageOfferEligibility = offerEligibility + + val presentedOverridesResult = component.overrides + .toPresentedOverrides(stripRules) { partial -> + PresentedPackagePartial(from = partial) + } + .mapError { nonEmptyListOf(it) } + val (stackComponentStyleResult, purchaseButtons) = withCount( predicate = { it is PurchaseButtonComponent }, ) { createStackComponentStyle(component.stack) } - stackComponentStyleResult.map { stack -> + zipOrAccumulate( + first = presentedOverridesResult, + second = stackComponentStyleResult, + ) { presentedOverrides, stack -> PackageComponentStyle( stackComponentStyle = stack, rcPackage = rcPackage, isSelectedByDefault = component.isSelectedByDefault, isSelectable = purchaseButtons == 0, resolvedOffer = resolvedOffer, + visible = component.visible ?: DEFAULT_VISIBILITY, + overrides = presentedOverrides, + offerEligibility = packageOfferEligibility, ) } } diff --git a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/helpers/OfferingToStateMapper.kt b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/helpers/OfferingToStateMapper.kt index 55f2bf13cf..eb0866dd2e 100644 --- a/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/helpers/OfferingToStateMapper.kt +++ b/ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/helpers/OfferingToStateMapper.kt @@ -463,7 +463,7 @@ internal fun PaywallComponent.containsUnsupportedCondition(): Boolean = when (th -> false } } - is PackageComponent -> stack.containsUnsupportedCondition() + is PackageComponent -> overrides.hasUnsupportedCondition() || stack.containsUnsupportedCondition() is PurchaseButtonComponent -> stack.containsUnsupportedCondition() is StickyFooterComponent -> stack.containsUnsupportedCondition() is CarouselComponent -> overrides.hasUnsupportedCondition() || diff --git a/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/VisibilityConditionTests.kt b/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/VisibilityConditionTests.kt index 1803011bcf..37e982524e 100644 --- a/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/VisibilityConditionTests.kt +++ b/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/VisibilityConditionTests.kt @@ -17,6 +17,7 @@ import com.revenuecat.purchases.paywalls.components.CarouselComponent import com.revenuecat.purchases.paywalls.components.IconComponent import com.revenuecat.purchases.paywalls.components.PackageComponent import com.revenuecat.purchases.paywalls.components.PartialCarouselComponent +import com.revenuecat.purchases.paywalls.components.PartialPackageComponent import com.revenuecat.purchases.paywalls.components.PartialStackComponent import com.revenuecat.purchases.paywalls.components.PartialTextComponent import com.revenuecat.purchases.paywalls.components.PartialTimelineComponent @@ -1961,4 +1962,104 @@ class VisibilityConditionTests { } // endregion + + // region Package visibility + + /** + * PackageComponent with visible=false is hidden. + */ + @Test + fun `Package hidden when visible is false`(): Unit = with(composeTestRule) { + val monthlyPkg = PackageComponent( + packageId = TestData.Packages.monthly.identifier, + isSelectedByDefault = false, + visible = false, + stack = StackComponent( + components = listOf(TextComponent(text = monthlyLabelKey, color = textColor)), + ), + ) + + val data = PaywallComponentsData( + id = "pkg_visible_false", + templateName = "components", + assetBaseURL = URL("https://assets.pawwalls.com"), + componentsConfig = ComponentsConfig( + base = PaywallComponentsConfig( + stack = StackComponent(components = listOf(monthlyPkg)), + background = Background.Color(ColorScheme(light = ColorInfo.Hex(Color.White.toArgb()))), + stickyFooter = null, + ), + ), + componentsLocalizations = localizations, + defaultLocaleIdentifier = localeId, + ) + val offering = Offering( + identifier = "pkg-visible-false", + serverDescription = "Package visible=false test", + metadata = emptyMap(), + availablePackages = listOf(TestData.Packages.monthly), + paywallComponents = Offering.PaywallComponents(UiConfig(), data), + ) + val validated = offering.validatePaywallComponentsDataOrNull()?.getOrThrow()!! + val state = offering.toComponentsPaywallState(validated) + val factory = StyleFactory(localizations = localizations, offering = offering) + val pkgStyle = factory.create(monthlyPkg).getOrThrow().componentStyle as PackageComponentStyle + + setContent { + PackageComponentView(style = pkgStyle, state = state, clickHandler = { }) + } + + onNodeWithText(monthlyLabelValue).assertDoesNotExist() + } + + /** + * PackageComponent with visible=true is shown. + */ + @Test + fun `Package visible when visible is true`(): Unit = with(composeTestRule) { + val monthlyPkg = PackageComponent( + packageId = TestData.Packages.monthly.identifier, + isSelectedByDefault = false, + visible = true, + stack = StackComponent( + components = listOf(TextComponent(text = monthlyLabelKey, color = textColor)), + ), + ) + + val data = PaywallComponentsData( + id = "pkg_visible_true", + templateName = "components", + assetBaseURL = URL("https://assets.pawwalls.com"), + componentsConfig = ComponentsConfig( + base = PaywallComponentsConfig( + stack = StackComponent(components = listOf(monthlyPkg)), + background = Background.Color(ColorScheme(light = ColorInfo.Hex(Color.White.toArgb()))), + stickyFooter = null, + ), + ), + componentsLocalizations = localizations, + defaultLocaleIdentifier = localeId, + ) + val offering = Offering( + identifier = "pkg-visible-true", + serverDescription = "Package visible=true test", + metadata = emptyMap(), + availablePackages = listOf(TestData.Packages.monthly), + paywallComponents = Offering.PaywallComponents(UiConfig(), data), + ) + val validated = offering.validatePaywallComponentsDataOrNull()?.getOrThrow()!! + val state = offering.toComponentsPaywallState(validated) + val factory = StyleFactory(localizations = localizations, offering = offering) + val pkgStyle = factory.create(monthlyPkg).getOrThrow().componentStyle as PackageComponentStyle + + setContent { + PackageComponentView(style = pkgStyle, state = state, clickHandler = { }) + } + + onNodeWithText(monthlyLabelValue).assertIsDisplayed() + } + + + // endregion + } diff --git a/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactoryTests.kt b/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactoryTests.kt index 31a5c68ae6..bcab2fa6aa 100644 --- a/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactoryTests.kt +++ b/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactoryTests.kt @@ -11,6 +11,7 @@ import com.revenuecat.purchases.paywalls.components.ButtonComponent import com.revenuecat.purchases.paywalls.components.ImageComponent import com.revenuecat.purchases.paywalls.components.PackageComponent import com.revenuecat.purchases.paywalls.components.PartialImageComponent +import com.revenuecat.purchases.paywalls.components.PartialPackageComponent import com.revenuecat.purchases.paywalls.components.PartialTextComponent import com.revenuecat.purchases.paywalls.components.PurchaseButtonComponent import com.revenuecat.purchases.paywalls.components.StackComponent @@ -1167,4 +1168,66 @@ class StyleFactoryTests { val secondImage = style.children[1] as ImageComponentStyle assertThat(secondImage.ignoreTopWindowInsets).isFalse() } + + @Test + fun `PackageComponentStyle visible defaults to true when component visible is null`() { + // Arrange + val packageComponent = PackageComponent( + packageId = "\$rc_annual", + isSelectedByDefault = false, + visible = null, + stack = StackComponent(components = emptyList()), + ) + + // Act + val result = styleFactory.create(packageComponent) + + // Assert + assertThat(result).isInstanceOf(Result.Success::class.java) + val pkgStyle = (result as Result.Success).value.componentStyle as PackageComponentStyle + assertThat(pkgStyle.visible).isTrue() + } + + @Test + fun `PackageComponentStyle visible is false when component visible is false`() { + // Arrange + val packageComponent = PackageComponent( + packageId = "\$rc_annual", + isSelectedByDefault = false, + visible = false, + stack = StackComponent(components = emptyList()), + ) + + // Act + val result = styleFactory.create(packageComponent) + + // Assert + assertThat(result).isInstanceOf(Result.Success::class.java) + val pkgStyle = (result as Result.Success).value.componentStyle as PackageComponentStyle + assertThat(pkgStyle.visible).isFalse() + } + + @Test + fun `PackageComponentStyle overrides are populated from component overrides`() { + // Arrange + val packageComponent = PackageComponent( + packageId = "\$rc_annual", + isSelectedByDefault = false, + stack = StackComponent(components = emptyList()), + overrides = listOf( + ComponentOverride( + conditions = listOf(ComponentOverride.Condition.IntroOffer), + properties = PartialPackageComponent(visible = false), + ), + ), + ) + + // Act + val result = styleFactory.create(packageComponent) + + // Assert + assertThat(result).isInstanceOf(Result.Success::class.java) + val pkgStyle = (result as Result.Success).value.componentStyle as PackageComponentStyle + assertThat(pkgStyle.overrides).hasSize(1) + } } diff --git a/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/helpers/ContainsUnsupportedConditionTests.kt b/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/helpers/ContainsUnsupportedConditionTests.kt index d5b7a93dfb..d3a5f2f88f 100644 --- a/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/helpers/ContainsUnsupportedConditionTests.kt +++ b/ui/revenuecatui/src/test/kotlin/com/revenuecat/purchases/ui/revenuecatui/helpers/ContainsUnsupportedConditionTests.kt @@ -7,6 +7,7 @@ import com.revenuecat.purchases.paywalls.components.IconComponent import com.revenuecat.purchases.paywalls.components.ImageComponent import com.revenuecat.purchases.paywalls.components.PackageComponent import com.revenuecat.purchases.paywalls.components.PartialImageComponent +import com.revenuecat.purchases.paywalls.components.PartialPackageComponent import com.revenuecat.purchases.paywalls.components.PartialStackComponent import com.revenuecat.purchases.paywalls.components.PartialTextComponent import com.revenuecat.purchases.paywalls.components.PurchaseButtonComponent @@ -288,6 +289,23 @@ internal class ContainsUnsupportedConditionTests { assertTrue(stack.containsUnsupportedCondition()) } + @Test + fun `PackageComponent detects unsupported in its own overrides`() { + val pkg = PackageComponent( + packageId = "monthly", + isSelectedByDefault = true, + stack = emptyStack(), + overrides = listOf( + ComponentOverride( + conditions = listOf(ComponentOverride.Condition.Unsupported), + properties = PartialPackageComponent(visible = false), + ) + ), + ) + val stack = emptyStack(components = listOf(pkg)) + assertTrue(stack.containsUnsupportedCondition()) + } + // endregion // region PurchaseButtonComponent