Files
OpenAppLock/SeveredUITests/UITestSupport.swift
Brendan Chen e6c87baeba feat: clone Opal's rules feature with hard block enforcement
Rebuild the app around recurring screen-time blocking rules modeled on
Opal's My Apps tab:

- Onboarding with FamilyControls Screen Time authorization
- Apps home with Blocked Apps tiles and live rule cards
- New Rule sheet: Schedule/Time Limit/Open Limit types + preset gallery
- Rule editors: time windows (incl. overnight), day picker, app selection
  via FamilyActivityPicker (Block/Allow Only), rename, Hold to Commit
- Hard Mode: active hard rules cannot be edited, disabled, deleted, or
  unblocked until their window ends; soft rules pause until next window
- Shield enforcement through per-rule ManagedSettingsStore + RuleEnforcer
- 73 unit tests (Swift Testing) + 16 UI tests (XCUITest) with launch-arg
  harness for in-memory storage, mocked authorization, seeded scenarios
- docs/RULES_FEATURE_SPEC.md: spec derived from the reference recording

Known gap: background window transitions and time/open-limit thresholds
need a DeviceActivityMonitor extension; shields currently sync while the
app is running.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-12 12:35:52 -04:00

49 lines
1.5 KiB
Swift

//
// UITestSupport.swift
// SeveredUITests
//
import XCTest
extension XCUIApplication {
/// Launches the app in UI-testing mode: in-memory storage, mocked Screen
/// Time authorization, and no shield side effects.
static func launchSevered(
onboardingCompleted: Bool = true,
seedScenario: String? = nil
) -> XCUIApplication {
let app = XCUIApplication()
var arguments = ["-ui-testing"]
arguments.append(onboardingCompleted ? "-onboarding-completed" : "-onboarding-required")
if let seedScenario {
arguments.append("-seed-scenario=\(seedScenario)")
}
app.launchArguments = arguments
app.launch()
return app
}
}
extension XCUIApplication {
/// Finds an element by accessibility identifier regardless of how SwiftUI
/// exposes it (Other, StaticText, Button, ).
func element(_ identifier: String) -> XCUIElement {
descendants(matching: .any).matching(identifier: identifier).firstMatch
}
}
extension XCUIElement {
/// Asserts the element appears within the timeout, then returns it.
@discardableResult
func waitToAppear(
timeout: TimeInterval = 5, file: StaticString = #filePath, line: UInt = #line
) -> XCUIElement {
XCTAssertTrue(
waitForExistence(timeout: timeout),
"Expected \(self) to exist within \(timeout)s",
file: file, line: line
)
return self
}
}