2.0: mostrar los dos ultimos pasos del onboarding (aha moment + paywall)

completeOnboarding escribia settings.onboardingCompleted al terminar el paso
de platos. Ese flag es el que ContentView observa para cambiar de OnboardingView
a HomeView, asi que el save destruia el flujo antes de que nextStep() pudiera
pintar el paso 5. WeekReadyStepView (el aha moment de la 1.2.0) y PaywallStepView
no se han visto nunca. GA4 lo confirma: 3 onboarding_completed en 28 dias y cero
eventos con step 5 o 6.

Se separa persistir los datos de marcar el onboarding como terminado:
completeOnboarding(markFinished:) commitea platos y plan sin tocar el flag, y el
nuevo finishOnboarding() lo escribe al salir del paywall. HomeView ya difiere su
prompt premium a la segunda sesion, asi que no hay paywall duplicado.

De paso, los nombres de paso en AnalyticsService estaban desalineados: el indice
5 decia "5_paywall" cuando el paso 5 es WeekReady, y el 6 no tenia nombre.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
This commit is contained in:
alexandrev-tibco
2026-08-05 20:53:10 +02:00
parent 10a3d8bf32
commit 3694297d4f
4 changed files with 97 additions and 6 deletions
@@ -1,4 +1,5 @@
import XCTest
import SwiftData
@testable import MealMood
@MainActor
@@ -41,4 +42,61 @@ final class OnboardingViewModelTests: XCTestCase {
XCTAssertEqual(vm.addedDishes.count, 1)
XCTAssertEqual(vm.addedDishes.first?.tagIds, [fallback.id])
}
// MARK: Onboarding completion flag
/// Returns the container, not just its context: releasing the container
/// leaves `mainContext` dangling and SwiftData traps on the next access.
private func makeContainer() throws -> ModelContainer {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
return try ModelContainer(
for: AppSettings.self, Tag.self, Dish.self, WeekPlan.self, MealSlot.self,
ShoppingItem.self,
configurations: config
)
}
/// Regression: the flag used to be written at the dishes step, which tore the
/// flow down before the Week Ready and paywall steps could render neither
/// was ever seen (confirmed in GA4: no step 5/6 events despite completions).
func testCompleteOnboardingKeepsFlowOnScreenWhenNotMarkedFinished() throws {
let container = try makeContainer()
let context = container.mainContext
let vm = OnboardingViewModel()
vm.newDishName = "Lentils"
vm.addDish(defaultTagId: UUID())
let saved = vm.completeOnboarding(context: context, markFinished: false)
XCTAssertTrue(saved)
let settings = try context.fetch(FetchDescriptor<AppSettings>()).first
XCTAssertNotNil(settings)
XCTAssertFalse(settings?.onboardingCompleted ?? true,
"The flag must stay false so the last two steps still render")
// The data itself is already committed at this point.
XCTAssertEqual(try context.fetch(FetchDescriptor<Dish>()).count, 1)
XCTAssertEqual(try context.fetch(FetchDescriptor<WeekPlan>()).count, 1)
}
func testFinishOnboardingSetsTheFlag() throws {
let container = try makeContainer()
let context = container.mainContext
let vm = OnboardingViewModel()
vm.completeOnboarding(context: context, markFinished: false)
vm.finishOnboarding(context: context)
let stored = try context.fetch(FetchDescriptor<AppSettings>()).first
XCTAssertTrue(stored?.onboardingCompleted ?? false)
}
func testCompleteOnboardingStillMarksFinishedByDefault() throws {
let container = try makeContainer()
let context = container.mainContext
OnboardingViewModel().completeOnboarding(context: context)
let stored = try context.fetch(FetchDescriptor<AppSettings>()).first
XCTAssertTrue(stored?.onboardingCompleted ?? false)
}
}