Tests: corrige 3 tests desfasados y añade recorrido UI del layout adaptativo
- ReviewPromptServiceTests: la regla vigente pide 2 check-ins, no 3. - MonthlyCheckInViewTests: el día 1 cae en el periodo de gracia (20 días); usar el día 25 para etiquetar febrero. - SettingsViewModelTests: isPremium llega por receive(on: main); dar una vuelta de run loop antes de actuar. - DuoLayoutUITests: recorre tabs, split views e inspector con capturas (ejecutar en iPhone 17 Pro y iPad mini). ScreenshotMode oculta los tips de TipKit; identificador de accesibilidad en el botón de Quick Update. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1u4K16xy7eQVtgsYNZ9Vn
This commit is contained in:
@@ -24,6 +24,9 @@ enum ScreenshotMode {
|
|||||||
// the premium charts without a StoreKit purchase. `--no-premium` keeps it
|
// the premium charts without a StoreKit purchase. `--no-premium` keeps it
|
||||||
// off to test the free-tier experience (locked charts, teasers).
|
// off to test the free-tier experience (locked charts, teasers).
|
||||||
d.set(!CommandLine.arguments.contains("--no-premium"), forKey: "debugPremiumOverride")
|
d.set(!CommandLine.arguments.contains("--no-premium"), forKey: "debugPremiumOverride")
|
||||||
|
// TipKit popovers cover toolbar buttons and swallow the first tap in
|
||||||
|
// automated walkthroughs — keep them out of captures.
|
||||||
|
Tips.hideAllTipsForTesting()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Seed demo data (no-op if the store already has sources). Called once Core Data
|
/// Seed demo data (no-op if the store already has sources). Called once Core Data
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ struct DashboardView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "plus.circle.fill")
|
Image(systemName: "plus.circle.fill")
|
||||||
}
|
}
|
||||||
|
.accessibilityIdentifier("dashboard.quickUpdate")
|
||||||
.popoverTip(QuickUpdateTip())
|
.popoverTip(QuickUpdateTip())
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ final class ReviewPromptServiceTests: XCTestCase {
|
|||||||
reviewRequestHandler: { didRequest = true }
|
reviewRequestHandler: { didRequest = true }
|
||||||
)
|
)
|
||||||
|
|
||||||
service.recordMonthlyCheckInCompleted()
|
// Rating velocity rule (1.6.x): 2 check-ins are enough, not 3.
|
||||||
service.recordMonthlyCheckInCompleted()
|
service.recordMonthlyCheckInCompleted()
|
||||||
XCTAssertFalse(didRequest)
|
XCTAssertFalse(didRequest)
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ final class SettingsViewModelTests: XCTestCase {
|
|||||||
iap.setDebugPremiumOverride(true)
|
iap.setDebugPremiumOverride(true)
|
||||||
#endif
|
#endif
|
||||||
let viewModel = SettingsViewModel(iapService: iap)
|
let viewModel = SettingsViewModel(iapService: iap)
|
||||||
|
// isPremium is mirrored via `receive(on: DispatchQueue.main)`; give the
|
||||||
|
// publisher a run-loop turn before acting on it.
|
||||||
|
RunLoop.main.run(until: Date().addingTimeInterval(0.2))
|
||||||
|
XCTAssertTrue(viewModel.isPremium)
|
||||||
|
|
||||||
viewModel.setBackupsEnabled(true)
|
viewModel.setBackupsEnabled(true)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ final class MonthlyCheckInViewTests: XCTestCase {
|
|||||||
var components = DateComponents()
|
var components = DateComponents()
|
||||||
components.year = 2026
|
components.year = 2026
|
||||||
components.month = 2
|
components.month = 2
|
||||||
components.day = 1
|
// Past the 20-day grace period (MonthlyCheckInStore.graceDays), so the
|
||||||
|
// effective month is February itself.
|
||||||
|
components.day = 25
|
||||||
let date = Calendar(identifier: .gregorian).date(from: components)!
|
let date = Calendar(identifier: .gregorian).date(from: components)!
|
||||||
|
|
||||||
let label = MonthlyCheckInView.monthLabel(
|
let label = MonthlyCheckInView.monthLabel(
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import XCTest
|
||||||
|
|
||||||
|
/// Walks the adaptive layout (tab bar / sidebar, split views, inspector) and
|
||||||
|
/// attaches a screenshot per step. Run on a compact device (iPhone) and on a
|
||||||
|
/// regular×regular one (iPad mini ≈ iPhone Duo inner screen) to compare.
|
||||||
|
final class DuoLayoutUITests: XCTestCase {
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
continueAfterFailure = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private func snap(_ app: XCUIApplication, _ name: String) {
|
||||||
|
let shot = XCTAttachment(screenshot: app.screenshot())
|
||||||
|
shot.name = name
|
||||||
|
shot.lifetime = .keepAlways
|
||||||
|
add(shot)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tab bar button on iPhone, top tab bar / sidebar row on iPad.
|
||||||
|
private func openTab(_ app: XCUIApplication, _ title: String) {
|
||||||
|
let tabBar = app.tabBars.firstMatch
|
||||||
|
if tabBar.exists && tabBar.buttons[title].exists && tabBar.buttons[title].isHittable {
|
||||||
|
tabBar.buttons[title].tap()
|
||||||
|
} else if app.buttons[title].firstMatch.waitForExistence(timeout: 3) {
|
||||||
|
app.buttons[title].firstMatch.tap()
|
||||||
|
} else if app.staticTexts[title].firstMatch.waitForExistence(timeout: 3) {
|
||||||
|
app.staticTexts[title].firstMatch.tap()
|
||||||
|
}
|
||||||
|
Thread.sleep(forTimeInterval: 1.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAdaptiveLayoutWalkthrough() throws {
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launchArguments = ["--screenshots"]
|
||||||
|
app.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: 8) {
|
||||||
|
denyButton.tap()
|
||||||
|
Thread.sleep(forTimeInterval: 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
snap(app, "01_dashboard")
|
||||||
|
|
||||||
|
// Quick Update: inspector column in regular width, sheet in compact.
|
||||||
|
let quickUpdate = app.buttons["dashboard.quickUpdate"].firstMatch
|
||||||
|
if quickUpdate.waitForExistence(timeout: 3) {
|
||||||
|
quickUpdate.tap()
|
||||||
|
Thread.sleep(forTimeInterval: 2.0)
|
||||||
|
snap(app, "02_quick_update_inspector")
|
||||||
|
let cancel = app.buttons["Cancel"].firstMatch
|
||||||
|
XCTAssertTrue(cancel.waitForExistence(timeout: 3), "Quick Update cancel button missing")
|
||||||
|
cancel.tap()
|
||||||
|
Thread.sleep(forTimeInterval: 1.5)
|
||||||
|
XCTAssertFalse(app.buttons["Cancel"].firstMatch.exists, "Quick Update did not dismiss")
|
||||||
|
snap(app, "03_dashboard_after_cancel")
|
||||||
|
} else {
|
||||||
|
XCTFail("Quick Update button not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
openTab(app, "Sources")
|
||||||
|
snap(app, "04_sources")
|
||||||
|
let sourceCells = app.cells
|
||||||
|
if sourceCells.count > 2 {
|
||||||
|
sourceCells.element(boundBy: 2).tap()
|
||||||
|
Thread.sleep(forTimeInterval: 2.0)
|
||||||
|
snap(app, "05_source_detail")
|
||||||
|
}
|
||||||
|
|
||||||
|
openTab(app, "Charts")
|
||||||
|
snap(app, "06_charts")
|
||||||
|
|
||||||
|
openTab(app, "Journal")
|
||||||
|
snap(app, "07_journal")
|
||||||
|
let journalCells = app.cells
|
||||||
|
if journalCells.count > 1 {
|
||||||
|
journalCells.element(boundBy: 1).tap()
|
||||||
|
Thread.sleep(forTimeInterval: 2.0)
|
||||||
|
snap(app, "08_journal_detail")
|
||||||
|
}
|
||||||
|
|
||||||
|
openTab(app, "Settings")
|
||||||
|
snap(app, "09_settings")
|
||||||
|
|
||||||
|
// Landscape: the Duo inner screen is regular×regular in both orientations.
|
||||||
|
XCUIDevice.shared.orientation = .landscapeLeft
|
||||||
|
Thread.sleep(forTimeInterval: 2.0)
|
||||||
|
openTab(app, "Home")
|
||||||
|
snap(app, "10_dashboard_landscape")
|
||||||
|
openTab(app, "Charts")
|
||||||
|
snap(app, "11_charts_landscape")
|
||||||
|
XCUIDevice.shared.orientation = .portrait
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user