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.
This commit is contained in:
48
SeveredUITests/UITestSupport.swift
Normal file
48
SeveredUITests/UITestSupport.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user