Files
FamilyMealPlanner/MealMood/Views/Onboarding/WelcomeStepView.swift
T
alexandrev-tibco 79423e3511 tests: target MealMoodUITests que recorre el onboarding completo
El bug de los pasos 5 y 6 era invisible a los tests unitarios: el contrato de
datos estaba bien, lo que fallaba era que la vista se desmontaba antes de pintar
el aha moment y el paywall. Hace falta recorrer la UI de verdad.

Anade el target MealMoodUITests (solo simulador, sin firma) y un test que hace
el onboarding entero en instalacion limpia y comprueba que Week Ready y el
paywall aparecen antes de la Home.

Los CTA del onboarding llevan ahora accessibilityIdentifier para que el test no
dependa de los textos, que estan en 6 idiomas.

Validado en los dos sentidos: pasa con el fix, y revirtiendo temporalmente
markFinished: false falla en la asercion de Week Ready. Suite completa: 27 tests.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ks8uUcMA9mjypVK7F2Pkt
2026-08-05 23:06:04 +02:00

167 lines
6.5 KiB
Swift

import SwiftUI
struct WelcomeStepView: View {
var onNext: () -> Void
var body: some View {
VStack(spacing: 0) {
Spacer()
AppIconPlaceholder(size: 96)
.padding(.bottom, 12)
Text("onboarding_welcome_subtitle")
.font(.mealMoodBody)
.foregroundColor(.mealMoodTextSecondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 32)
.padding(.bottom, 24)
weekPreview
.padding(.bottom, 24)
VStack(alignment: .leading, spacing: 14) {
BenefitRow(icon: "wand.and.stars", text: String(localized: "onboarding_welcome_step_1"))
BenefitRow(icon: "square.and.arrow.up", text: String(localized: "onboarding_welcome_step_3"))
BenefitRow(icon: "icloud", text: String(localized: "onboarding_welcome_step_4"))
}
.padding(.horizontal, 40)
Spacer()
PrimaryButton(title: String(localized: "onboarding_start"), icon: "", action: onNext)
.accessibilityIdentifier("onboarding_cta_welcome")
.padding(.horizontal, 24)
.padding(.bottom, 40)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
}
// MARK: - Week preview
private var weekPreview: some View {
VStack(alignment: .leading, spacing: 8) {
Text(String(localized: "onboarding_welcome_preview_label"))
.font(.mealMoodCaption)
.foregroundColor(.mealMoodTextSecondary)
.padding(.horizontal, 24)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 6) {
ForEach(sampleDays, id: \.name) { day in
VStack(spacing: 4) {
Text(day.name)
.font(.system(size: 10, weight: .bold))
.foregroundColor(.mealMoodTextPrimary)
.frame(maxWidth: .infinity)
.padding(.vertical, 5)
.background(day.color)
.clipShape(RoundedRectangle(cornerRadius: 8))
Text(day.dish)
.font(.system(size: 10, weight: .medium))
.foregroundColor(.mealMoodTextPrimary)
.multilineTextAlignment(.center)
.lineLimit(2)
.minimumScaleFactor(0.8)
.frame(maxWidth: .infinity, minHeight: 38)
.padding(.horizontal, 4)
.padding(.vertical, 5)
.background(Color.white.opacity(0.92))
.clipShape(RoundedRectangle(cornerRadius: 8))
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color(hex: "#EEE4DE"), lineWidth: 1)
)
}
.frame(width: 66)
}
}
.padding(.horizontal, 20)
.padding(.vertical, 8)
}
.background(
RoundedRectangle(cornerRadius: 14)
.fill(Color.mealMoodBackground)
.overlay(
RoundedRectangle(cornerRadius: 14)
.stroke(Color.mealMoodCoral.opacity(0.25), lineWidth: 1)
)
)
.padding(.horizontal, 16)
}
}
private var sampleDays: [(name: String, dish: String, color: Color)] {
let lang = Locale.current.language.languageCode?.identifier ?? "en"
switch lang {
case "es":
return [
("Lun", "Pasta al horno", Color(hex: "#FFD5D9")),
("Mar", "Pollo asado", Color(hex: "#FFE1C8")),
("Mié", "Lentejas", Color(hex: "#FFF0C8")),
("Jue", "Salmón", Color(hex: "#DFF2CC")),
("Vie", "Tortilla", Color(hex: "#D3F0E7"))
]
case "fr":
return [
("Lun", "Pâtes gratinées", Color(hex: "#FFD5D9")),
("Mar", "Poulet rôti", Color(hex: "#FFE1C8")),
("Mer", "Lentilles", Color(hex: "#FFF0C8")),
("Jeu", "Saumon", Color(hex: "#DFF2CC")),
("Ven", "Omelette", Color(hex: "#D3F0E7"))
]
case "de":
return [
("Mo", "Nudeln", Color(hex: "#FFD5D9")),
("Di", "Hähnchen", Color(hex: "#FFE1C8")),
("Mi", "Linsensuppe", Color(hex: "#FFF0C8")),
("Do", "Lachs", Color(hex: "#DFF2CC")),
("Fr", "Omelett", Color(hex: "#D3F0E7"))
]
case "it":
return [
("Lun", "Pasta al forno", Color(hex: "#FFD5D9")),
("Mar", "Pollo arrosto", Color(hex: "#FFE1C8")),
("Mer", "Lenticchie", Color(hex: "#FFF0C8")),
("Gio", "Salmone", Color(hex: "#DFF2CC")),
("Ven", "Frittata", Color(hex: "#D3F0E7"))
]
case "pt":
return [
("Seg", "Macarrão", Color(hex: "#FFD5D9")),
("Ter", "Frango assado", Color(hex: "#FFE1C8")),
("Qua", "Lentilhas", Color(hex: "#FFF0C8")),
("Qui", "Salmão", Color(hex: "#DFF2CC")),
("Sex", "Omelete", Color(hex: "#D3F0E7"))
]
default:
return [
("Mon", "Baked pasta", Color(hex: "#FFD5D9")),
("Tue", "Roast chicken", Color(hex: "#FFE1C8")),
("Wed", "Lentil soup", Color(hex: "#FFF0C8")),
("Thu", "Salmon", Color(hex: "#DFF2CC")),
("Fri", "Omelette", Color(hex: "#D3F0E7"))
]
}
}
}
private struct BenefitRow: View {
let icon: String
let text: String
var body: some View {
HStack(spacing: 12) {
Image(systemName: icon)
.font(.system(size: 18))
.foregroundColor(.mealMoodCoral)
.frame(width: 28)
Text(text)
.font(.mealMoodBody)
.foregroundColor(.mealMoodTextPrimary)
}
}
}