Fix causa raíz de taps muertos en Charts iPad: AppBackground interceptaba toques (build 50)

El círculo decorativo de AppBackground (70% del ancho del panel, offset -35%)
se desborda de su panel por diseño y en layouts side-by-side queda flotando
SOBRE el sidebar de Charts. Las shapes de SwiftUI participan en hit-testing por
defecto y el panel derecho va después en el HStack → cada tap en la zona del
sidebar cubierta por el círculo moría en silencio. El patrón lo delató: fallaban
Overview + Analyze (arriba, bajo el círculo) y funcionaban Risk + Forecast
(abajo). Dependía de la geometría (orientación/tamaño), por eso no reproducía
en el simulador en portrait.

- AppBackground: .allowsHitTesting(false) — un fondo decorativo jamás debe
  interceptar toques (fix global: aplica también a Sources/Journal en iPad)
- Panel de detalle de Charts: .clipped() para que la decoración tampoco PINTE
  sobre el sidebar
- Selección premium nunca se bloquea: los charts premium se seleccionan y
  muestran teaser de desbloqueo en el área del chart (chart_locked_* ×7 idiomas);
  el paywall se presenta desde el botón (contexto fiable)
- UITests: testChartSidebarSelection ahora corre en landscape (geometría que
  reproducía el bug) + testChartSelectionWithoutPremium nuevo; --no-premium
  en ScreenshotMode para testear la experiencia free

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoScpmHdVj1aUf4rAp6hbe
This commit is contained in:
alexandrev-tibco
2026-07-08 23:30:30 +02:00
parent 7b8dec196d
commit ff67ea1e71
13 changed files with 147 additions and 22 deletions
@@ -193,6 +193,9 @@ final class PortfolioJournalUITests: XCTestCase {
/// Charts sidebar must actually switch the chart taps on the old custom tiles
/// were unreliable on iPad. Also verifies the Rolling 12M period filter reacts.
func testChartSidebarSelection() throws {
// Landscape reproduces the decorative-background overlay geometry that
// killed sidebar taps (AppBackground circle overflowing its panel).
XCUIDevice.shared.orientation = .landscapeLeft
let capture = XCUIApplication()
capture.launchArguments = ["--screenshots"]
capture.launch()
@@ -257,6 +260,55 @@ final class PortfolioJournalUITests: XCTestCase {
}
}
/// Regression (1.4.2): WITHOUT premium, tapping a premium chart must still
/// select it and show the unlock teaser blocking selection behind a paywall
/// sheet made premium tiles look completely dead on non-premium devices.
func testChartSelectionWithoutPremium() throws {
let capture = XCUIApplication()
capture.launchArguments = ["--screenshots", "--no-premium"]
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)
}
let tabBar = capture.tabBars.firstMatch
if tabBar.exists && tabBar.buttons["Charts"].exists {
tabBar.buttons["Charts"].tap()
} else {
capture.staticTexts["Charts"].firstMatch.tap()
}
Thread.sleep(forTimeInterval: 2.0)
// Premium chart: must select and show the unlock teaser (not a dead tap)
let allocationRow = capture.staticTexts["Allocation"].firstMatch
XCTAssertTrue(allocationRow.waitForExistence(timeout: 5), "Allocation row not found in sidebar")
allocationRow.tap()
Thread.sleep(forTimeInterval: 2.0)
let shot = XCTAttachment(screenshot: capture.screenshot())
shot.name = "charts_allocation_locked"
shot.lifetime = .keepAlways
add(shot)
XCTAssertTrue(
capture.buttons["Unlock Premium"].firstMatch.waitForExistence(timeout: 5),
"Premium chart tap did not select the chart / show the unlock teaser"
)
// The unlock button must present the paywall
capture.buttons["Unlock Premium"].firstMatch.tap()
Thread.sleep(forTimeInterval: 2.0)
let paywallShot = XCTAttachment(screenshot: capture.screenshot())
paywallShot.name = "charts_paywall"
paywallShot.lifetime = .keepAlways
add(paywallShot)
}
/// 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