Calm 2.0 Fase 2: check-in guiado full-screen (build 52)
El ritual mensual como flujo paso a paso en vez de formulario: - Una fuente por pantalla: valor anterior a la vista, entrada grande (40pt rounded), delta en vivo (chip verde/rojo con importe y %), sugerencia de portapapeles, 'Igual que el mes pasado' y 'Omitir' - Barra de progreso del ritual; cancelable solo al inicio (interactiveDismiss bloqueado a mitad para no perder valores) - Reflexión: mood (5 estados existentes) + nota de una línea - Cierre: total nuevo, delta del mes, streak, compartir y haptic de éxito - Persistencia por los MISMOS caminos que Quick Update (Snapshots) y el journal (MonthlyCheckInStore → JournalEntry): coherente con el resto de entradas - Entrada: CTA del check-in card en Home (fullScreenCover); la fila de completado sigue navegando al journal - Fix: referenceDate congelada en @State al presentar — el card la recalcula al guardar y SwiftUI re-evaluaba el contenido del cover cambiando el mes a mitad de flujo - --pending-checkin en ScreenshotMode (revierte el seed a 'mes pendiente') + UITest testGuidedCheckInFlow end-to-end (valor → skips → mood → cierre) ✓ - Strings ×7 idiomas Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
This commit is contained in:
@@ -309,6 +309,83 @@ final class PortfolioJournalUITests: XCTestCase {
|
||||
add(paywallShot)
|
||||
}
|
||||
|
||||
/// Calm 2.0 Fase 2: walks the guided check-in end to end — enter a value on
|
||||
/// the first source (live delta), skip the rest, pick a mood, finish, and
|
||||
/// verify the closing summary appears. `--pending-checkin` puts the seeded
|
||||
/// data back into a "month not done" state.
|
||||
func testGuidedCheckInFlow() throws {
|
||||
XCUIDevice.shared.orientation = .portrait
|
||||
let capture = XCUIApplication()
|
||||
capture.launchArguments = ["--screenshots", "--pending-checkin"]
|
||||
capture.launch()
|
||||
Thread.sleep(forTimeInterval: 4.0)
|
||||
|
||||
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
|
||||
let denyButton = springboard.alerts.buttons["Don't Allow"]
|
||||
if denyButton.waitForExistence(timeout: 3) {
|
||||
denyButton.tap()
|
||||
Thread.sleep(forTimeInterval: 1.0)
|
||||
}
|
||||
|
||||
func snap(_ name: String) {
|
||||
let shot = XCTAttachment(screenshot: capture.screenshot())
|
||||
shot.name = name
|
||||
shot.lifetime = .keepAlways
|
||||
add(shot)
|
||||
}
|
||||
|
||||
snap("guided_00_home_pending")
|
||||
|
||||
let cta = capture.buttons["checkin_start_cta"].firstMatch
|
||||
XCTAssertTrue(cta.waitForExistence(timeout: 5), "Pending check-in CTA not found on Home")
|
||||
cta.tap()
|
||||
Thread.sleep(forTimeInterval: 1.5)
|
||||
|
||||
// First source: type a value, verify Next enables, advance
|
||||
let valueField = capture.textFields["guided_value_field"].firstMatch
|
||||
XCTAssertTrue(valueField.waitForExistence(timeout: 5), "Guided flow value field not found")
|
||||
valueField.tap()
|
||||
valueField.typeText("12345")
|
||||
Thread.sleep(forTimeInterval: 0.5)
|
||||
snap("guided_01_source_value")
|
||||
|
||||
let next = capture.buttons["guided_next_btn"].firstMatch
|
||||
XCTAssertTrue(next.isEnabled, "Next should enable after typing a value")
|
||||
next.tap()
|
||||
Thread.sleep(forTimeInterval: 0.8)
|
||||
|
||||
// Skip the remaining sources until the reflection step appears
|
||||
let finish = capture.buttons["guided_finish_btn"].firstMatch
|
||||
var hops = 0
|
||||
while !finish.exists && hops < 20 {
|
||||
let skip = capture.buttons["guided_skip_btn"].firstMatch
|
||||
guard skip.waitForExistence(timeout: 2) else { break }
|
||||
skip.tap()
|
||||
Thread.sleep(forTimeInterval: 0.6)
|
||||
hops += 1
|
||||
}
|
||||
XCTAssertTrue(finish.waitForExistence(timeout: 5), "Reflection step not reached")
|
||||
snap("guided_02_reflection")
|
||||
|
||||
// Pick a mood and finish
|
||||
let mood = capture.otherElements["guided_mood_balanced"].firstMatch
|
||||
if mood.exists {
|
||||
mood.tap()
|
||||
} else if capture.buttons["guided_mood_balanced"].firstMatch.exists {
|
||||
capture.buttons["guided_mood_balanced"].firstMatch.tap()
|
||||
}
|
||||
Thread.sleep(forTimeInterval: 0.5)
|
||||
finish.tap()
|
||||
Thread.sleep(forTimeInterval: 2.0)
|
||||
snap("guided_03_done")
|
||||
|
||||
XCTAssertTrue(
|
||||
capture.staticTexts.matching(NSPredicate(format: "label CONTAINS[c] 'check-in done'")).firstMatch
|
||||
.waitForExistence(timeout: 5),
|
||||
"Closing summary did not appear after finishing the check-in"
|
||||
)
|
||||
}
|
||||
|
||||
/// Captures full-screen screenshots of the main tabs with demo data for App Store
|
||||
/// marketing. Launches the app in `--screenshots` mode (onboarding/lock skipped,
|
||||
/// SampleDataService seeded). Language can be driven via the SCREENSHOT_LANG env var
|
||||
|
||||
Reference in New Issue
Block a user