P2 Stream E: FeatureComparisonScreen (replaces FeatureComparisonDialog)
Full-screen feature comparison matching iOS FeatureComparisonView. Two-column table, iOS-equivalent row set, CTA to upgrade flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+132
@@ -0,0 +1,132 @@
|
||||
package com.tt.honeyDue.ui.screens.subscription
|
||||
|
||||
import com.tt.honeyDue.analytics.AnalyticsEvents
|
||||
import com.tt.honeyDue.models.FeatureBenefit
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* P2 Stream E — FeatureComparisonScreen tests.
|
||||
*
|
||||
* These tests exercise the state-logic backing the full-screen feature
|
||||
* comparison (replaces the old FeatureComparisonDialog). They use plain
|
||||
* kotlin.test rather than Compose UI testing for the same reasons cited
|
||||
* in ThemeSelectionScreenTest — the commonTest recomposer/Dispatchers
|
||||
* interplay is flaky on iosSimulator.
|
||||
*
|
||||
* Mirrors iOS `iosApp/iosApp/Subscription/FeatureComparisonView.swift`.
|
||||
*/
|
||||
class FeatureComparisonScreenTest {
|
||||
|
||||
// 1. The default feature-row set matches iOS FeatureComparisonView.swift
|
||||
// lines 99-105 (shown when DataManager.featureBenefits is empty).
|
||||
@Test
|
||||
fun defaultFeatureRowsMatchIOSOrderAndText() {
|
||||
val rows = FeatureComparisonScreenState.defaultFeatureRows()
|
||||
|
||||
assertEquals(4, rows.size, "iOS default list has 4 rows")
|
||||
|
||||
assertEquals("Properties", rows[0].featureName)
|
||||
assertEquals("1 property", rows[0].freeTierText)
|
||||
assertEquals("Unlimited", rows[0].proTierText)
|
||||
|
||||
assertEquals("Tasks", rows[1].featureName)
|
||||
assertEquals("10 tasks", rows[1].freeTierText)
|
||||
assertEquals("Unlimited", rows[1].proTierText)
|
||||
|
||||
assertEquals("Contractors", rows[2].featureName)
|
||||
assertEquals("Not available", rows[2].freeTierText)
|
||||
assertEquals("Unlimited", rows[2].proTierText)
|
||||
|
||||
assertEquals("Documents", rows[3].featureName)
|
||||
assertEquals("Not available", rows[3].freeTierText)
|
||||
assertEquals("Unlimited", rows[3].proTierText)
|
||||
}
|
||||
|
||||
// 2. freeHasFeature returns false for "Not available" rows (the iOS
|
||||
// comparison renders the free column grey for those), true for
|
||||
// rows with an actual limit text like "1 property".
|
||||
@Test
|
||||
fun freeHasFeatureFollowsIosPerRowBooleans() {
|
||||
val rows = FeatureComparisonScreenState.defaultFeatureRows()
|
||||
|
||||
assertTrue(FeatureComparisonScreenState.freeHasFeature(rows[0]), "Properties: free has 1")
|
||||
assertTrue(FeatureComparisonScreenState.freeHasFeature(rows[1]), "Tasks: free has 10")
|
||||
assertFalse(FeatureComparisonScreenState.freeHasFeature(rows[2]), "Contractors: free has none")
|
||||
assertFalse(FeatureComparisonScreenState.freeHasFeature(rows[3]), "Documents: free has none")
|
||||
}
|
||||
|
||||
// 3. premiumHasFeature is true for every row on iOS — Pro is unlimited
|
||||
// across the default set and every server-driven benefit.
|
||||
@Test
|
||||
fun premiumHasFeatureAlwaysTrueForProTier() {
|
||||
val rows = FeatureComparisonScreenState.defaultFeatureRows()
|
||||
rows.forEach { row ->
|
||||
assertTrue(
|
||||
FeatureComparisonScreenState.premiumHasFeature(row),
|
||||
"Pro always has ${row.featureName}"
|
||||
)
|
||||
}
|
||||
|
||||
// Server-driven benefits: Pro tier is true unless the text is
|
||||
// explicitly "Not available" (treated the same as the Free column).
|
||||
val benefit = FeatureBenefit(
|
||||
featureName = "Reports",
|
||||
freeTierText = "Not available",
|
||||
proTierText = "Not available"
|
||||
)
|
||||
assertFalse(FeatureComparisonScreenState.premiumHasFeature(benefit))
|
||||
}
|
||||
|
||||
// 4. CTA invocation calls the expected navigation callback.
|
||||
@Test
|
||||
fun ctaInvokesUpgradeNavigationCallback() {
|
||||
var navigated = false
|
||||
FeatureComparisonScreenState.onUpgradeTap(
|
||||
onNavigateToUpgrade = { navigated = true },
|
||||
captureEvent = { _, _ -> /* ignore */ },
|
||||
)
|
||||
assertTrue(navigated, "Upgrade CTA must navigate to upgrade flow")
|
||||
}
|
||||
|
||||
// 5. Upgrade CTA fires analytics event paywall_compare_cta.
|
||||
@Test
|
||||
fun ctaFiresPaywallCompareAnalyticsEvent() {
|
||||
val captured = mutableListOf<Pair<String, Map<String, Any>?>>()
|
||||
FeatureComparisonScreenState.onUpgradeTap(
|
||||
onNavigateToUpgrade = { },
|
||||
captureEvent = { event, props -> captured.add(event to props) },
|
||||
)
|
||||
assertEquals(1, captured.size, "Exactly one analytics event")
|
||||
assertEquals(AnalyticsEvents.PAYWALL_COMPARE_CTA, captured[0].first)
|
||||
}
|
||||
|
||||
// 6. Close / back callback is invoked when the user dismisses the
|
||||
// screen (matches iOS "Close" toolbar button).
|
||||
@Test
|
||||
fun onCloseInvokesBackCallback() {
|
||||
var closed = false
|
||||
FeatureComparisonScreenState.onClose(onBack = { closed = true })
|
||||
assertTrue(closed, "Close/Back must call the onBack callback")
|
||||
}
|
||||
|
||||
// 7. When DataManager has server-driven benefits, the screen uses
|
||||
// those in preference to the default list.
|
||||
@Test
|
||||
fun serverDrivenBenefitsOverrideDefaults() {
|
||||
val serverBenefits = listOf(
|
||||
FeatureBenefit("Custom Feature A", "Not available", "Unlimited"),
|
||||
FeatureBenefit("Custom Feature B", "5 items", "Unlimited"),
|
||||
)
|
||||
val effective = FeatureComparisonScreenState.resolveFeatureRows(serverBenefits)
|
||||
assertEquals(2, effective.size)
|
||||
assertEquals("Custom Feature A", effective[0].featureName)
|
||||
assertFalse(FeatureComparisonScreenState.freeHasFeature(effective[0]))
|
||||
assertTrue(FeatureComparisonScreenState.freeHasFeature(effective[1]))
|
||||
|
||||
val empty = FeatureComparisonScreenState.resolveFeatureRows(emptyList())
|
||||
assertEquals(4, empty.size, "Empty benefits falls back to default iOS list")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user