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>
This commit is contained in:
36
SeveredTests/TestSupport.swift
Normal file
36
SeveredTests/TestSupport.swift
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// TestSupport.swift
|
||||
// SeveredTests
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable import Severed
|
||||
|
||||
/// Fixed UTC gregorian calendar so schedule math is deterministic regardless
|
||||
/// of the machine running the tests.
|
||||
let utc: Calendar = {
|
||||
var calendar = Calendar(identifier: .gregorian)
|
||||
calendar.timeZone = TimeZone(identifier: "UTC")!
|
||||
return calendar
|
||||
}()
|
||||
|
||||
/// Builds a date in the UTC test calendar. 2025-01-06 is a Monday; the tests
|
||||
/// use that week (Jan 6–12, 2025) as their anchor.
|
||||
func date(
|
||||
_ year: Int, _ month: Int, _ day: Int, _ hour: Int = 0, _ minute: Int = 0
|
||||
) -> Date {
|
||||
utc.date(
|
||||
from: DateComponents(year: year, month: month, day: day, hour: hour, minute: minute)
|
||||
)!
|
||||
}
|
||||
|
||||
/// Monday of the anchor week.
|
||||
enum AnchorWeek {
|
||||
static let monday = (year: 2025, month: 1, day: 6)
|
||||
static let tuesday = (year: 2025, month: 1, day: 7)
|
||||
static let wednesday = (year: 2025, month: 1, day: 8)
|
||||
static let saturday = (year: 2025, month: 1, day: 11)
|
||||
static let sunday = (year: 2025, month: 1, day: 12)
|
||||
static let nextMonday = (year: 2025, month: 1, day: 13)
|
||||
}
|
||||
Reference in New Issue
Block a user