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>
100 lines
4.0 KiB
Swift
100 lines
4.0 KiB
Swift
//
|
|
// RuleCreationUITests.swift
|
|
// SeveredUITests
|
|
//
|
|
|
|
import XCTest
|
|
|
|
final class RuleCreationUITests: XCTestCase {
|
|
override func setUpWithError() throws {
|
|
continueAfterFailure = false
|
|
}
|
|
|
|
func testCreateScheduleRuleFromTypeCard() throws {
|
|
let app = XCUIApplication.launchSevered()
|
|
app.element("emptyRulesCard").waitToAppear()
|
|
|
|
app.buttons["newRuleButton"].tap()
|
|
app.staticTexts["New Rule"].waitToAppear()
|
|
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
|
|
|
// Editor opens with the schedule defaults.
|
|
XCTAssertEqual(app.staticTexts["ruleEditorTitle"].waitToAppear().label, "In the Zone")
|
|
XCTAssertTrue(app.staticTexts["During this time"].exists)
|
|
|
|
// Hold to commit saves the rule and returns home.
|
|
app.buttons["holdToCommitButton"].waitToAppear().press(forDuration: 2.0)
|
|
app.buttons["ruleCard-In the Zone"].waitToAppear()
|
|
XCTAssertFalse(app.element("emptyRulesCard").exists)
|
|
}
|
|
|
|
func testCreateRuleFromPreset() throws {
|
|
let app = XCUIApplication.launchSevered()
|
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
|
|
|
app.buttons["preset-work-time"].waitToAppear().tap()
|
|
XCTAssertEqual(app.staticTexts["ruleEditorTitle"].waitToAppear().label, "Work Time")
|
|
|
|
app.buttons["holdToCommitButton"].waitToAppear().press(forDuration: 2.0)
|
|
app.buttons["ruleCard-Work Time"].waitToAppear()
|
|
}
|
|
|
|
func testRenameRuleInEditor() throws {
|
|
let app = XCUIApplication.launchSevered()
|
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
|
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
|
|
|
app.buttons["renameButton"].waitToAppear().tap()
|
|
let nameField = app.textFields.firstMatch.waitToAppear()
|
|
nameField.tap()
|
|
// Clear the prefilled name, then type the new one.
|
|
let deletions = String(repeating: XCUIKeyboardKey.delete.rawValue, count: 24)
|
|
nameField.typeText(deletions + "My Focus")
|
|
app.buttons["OK"].tap()
|
|
|
|
XCTAssertEqual(app.staticTexts["ruleEditorTitle"].label, "My Focus")
|
|
app.buttons["holdToCommitButton"].waitToAppear().press(forDuration: 2.0)
|
|
app.buttons["ruleCard-My Focus"].waitToAppear()
|
|
}
|
|
|
|
func testDayTogglesUpdateSummary() throws {
|
|
let app = XCUIApplication.launchSevered()
|
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
|
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
|
|
|
// Default is Weekdays; enabling Saturday and Sunday makes it Every day.
|
|
XCTAssertTrue(app.staticTexts["Weekdays"].waitToAppear().exists)
|
|
app.buttons["dayToggle-1"].tap()
|
|
app.buttons["dayToggle-7"].tap()
|
|
XCTAssertTrue(app.staticTexts["Every day"].waitToAppear().exists)
|
|
}
|
|
|
|
func testCreateTimeLimitRule() throws {
|
|
let app = XCUIApplication.launchSevered()
|
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
|
app.buttons["ruleKind-timeLimit"].waitToAppear().tap()
|
|
|
|
XCTAssertEqual(app.staticTexts["ruleEditorTitle"].waitToAppear().label, "Time Keeper")
|
|
XCTAssertTrue(app.staticTexts["When I use"].exists)
|
|
XCTAssertEqual(app.staticTexts["dailyLimitStepperValue"].label, "45m")
|
|
|
|
app.steppers["dailyLimitStepper"].buttons["Increment"].tap()
|
|
XCTAssertEqual(app.staticTexts["dailyLimitStepperValue"].label, "60m")
|
|
|
|
app.buttons["holdToCommitButton"].waitToAppear().press(forDuration: 2.0)
|
|
app.buttons["ruleCard-Time Keeper"].waitToAppear()
|
|
}
|
|
|
|
func testNewRuleSheetShowsTypesAndPresets() throws {
|
|
let app = XCUIApplication.launchSevered()
|
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
|
|
|
app.buttons["ruleKind-schedule"].waitToAppear()
|
|
XCTAssertTrue(app.buttons["ruleKind-timeLimit"].exists)
|
|
XCTAssertTrue(app.buttons["ruleKind-openLimit"].exists)
|
|
XCTAssertTrue(app.staticTexts["Get More Done"].exists)
|
|
XCTAssertTrue(app.buttons["preset-work-time"].exists)
|
|
XCTAssertTrue(app.buttons["preset-laser-focus"].exists)
|
|
}
|
|
}
|