Replace the custom themed presentation with the bare native iOS design language across a three-tab TabView (Home / Rules / Settings): - MainTabView hosts the enforcement lifecycle (refresh loop, rule-change and scene-active reconcile) so it runs regardless of the selected tab - HomeView: "Currently Blocking" + "Usage" sections (replaces AppsHomeView) - RulesListView: rules grouped into Schedule / Time Limit / Open Limit - SettingsView: Uninstall Protection toggle + Manage App Lists - AppListLibraryView / ManageAppListsView: shared App List management - AppSettings: app-group-backed settings store Uninstall Protection: while enabled and any Hard Mode rule is actively blocking, deny device app-removal via a dedicated ManagedSettingsStore (RulePolicy.shouldDenyAppRemoval -> RuleEnforcer -> ShieldController). Slim AppListPickerSheet to the picker role; drop AppsHomeView and the custom Theme/HoldToCommitButton/RuleCardView chrome. Update spec section 6 and the unit/UI test suites accordingly.
198 lines
8.2 KiB
Swift
198 lines
8.2 KiB
Swift
//
|
|
// RuleCreationUITests.swift
|
|
// OpenAppLockUITests
|
|
//
|
|
|
|
import XCTest
|
|
|
|
final class RuleCreationUITests: XCTestCase {
|
|
override func setUpWithError() throws {
|
|
continueAfterFailure = false
|
|
}
|
|
|
|
func testCreateScheduleRuleFromTypeCard() throws {
|
|
let app = XCUIApplication.launchOpenAppLock()
|
|
app.goToRulesTab()
|
|
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)
|
|
|
|
// The confirmation checkmark lives in the navigation bar and carries
|
|
// a descriptive accessibility label.
|
|
let commit = app.navigationBars.buttons["commitRuleButton"].waitToAppear()
|
|
XCTAssertEqual(commit.label, "Add Rule")
|
|
commit.tap()
|
|
app.buttons["ruleCard-In the Zone"].waitToAppear()
|
|
XCTAssertFalse(app.element("emptyRulesCard").exists)
|
|
}
|
|
|
|
func testCreateRuleFromPreset() throws {
|
|
let app = XCUIApplication.launchOpenAppLock()
|
|
app.goToRulesTab()
|
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
|
|
|
app.buttons["preset-work-time"].waitToAppear().tap()
|
|
XCTAssertEqual(app.staticTexts["ruleEditorTitle"].waitToAppear().label, "Work Time")
|
|
|
|
app.buttons["commitRuleButton"].waitToAppear().tap()
|
|
app.buttons["ruleCard-Work Time"].waitToAppear()
|
|
}
|
|
|
|
func testRenameRuleInEditor() throws {
|
|
let app = XCUIApplication.launchOpenAppLock()
|
|
app.goToRulesTab()
|
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
|
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
|
|
|
// The rule name is an inline text field at the top of the editor —
|
|
// no separate edit/rename button.
|
|
XCTAssertFalse(app.buttons["renameButton"].exists)
|
|
let nameField = app.textFields["ruleNameField"].waitToAppear()
|
|
// Tap at the trailing edge so the cursor lands after the last character.
|
|
nameField.coordinate(withNormalizedOffset: CGVector(dx: 0.95, dy: 0.5)).tap()
|
|
let deletions = String(repeating: XCUIKeyboardKey.delete.rawValue, count: 24)
|
|
nameField.typeText(deletions + "My Focus\n")
|
|
|
|
XCTAssertEqual(app.staticTexts["ruleEditorTitle"].label, "My Focus")
|
|
app.buttons["commitRuleButton"].waitToAppear().tap()
|
|
app.buttons["ruleCard-My Focus"].waitToAppear()
|
|
}
|
|
|
|
func testDayTogglesFillRowAndHaveLargeTapTargets() throws {
|
|
let app = XCUIApplication.launchOpenAppLock()
|
|
app.goToRulesTab()
|
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
|
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
|
|
|
let first = app.buttons["dayToggle-1"].waitToAppear()
|
|
let last = app.buttons["dayToggle-7"].waitToAppear()
|
|
let span = last.frame.maxX - first.frame.minX
|
|
XCTAssertGreaterThan(
|
|
span, app.frame.width * 0.75,
|
|
"Day toggles should span the full row width, got \(span) of \(app.frame.width)"
|
|
)
|
|
XCTAssertGreaterThanOrEqual(
|
|
first.frame.height, 44,
|
|
"Day toggle tap target should be at least 44pt tall"
|
|
)
|
|
}
|
|
|
|
func testDayTogglesUpdateSummary() throws {
|
|
let app = XCUIApplication.launchOpenAppLock()
|
|
app.goToRulesTab()
|
|
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.launchOpenAppLock()
|
|
app.goToRulesTab()
|
|
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["commitRuleButton"].waitToAppear().tap()
|
|
app.buttons["ruleCard-Time Keeper"].waitToAppear()
|
|
}
|
|
|
|
func testAdultContentToggleFlowsToDetail() throws {
|
|
let app = XCUIApplication.launchOpenAppLock()
|
|
app.goToRulesTab()
|
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
|
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
|
|
|
// The toggle lives at the bottom of the form; scroll it clear of the
|
|
// commit bar before tapping.
|
|
app.staticTexts["ruleEditorTitle"].waitToAppear()
|
|
app.swipeUp()
|
|
app.switches["adultContentToggle"].waitToAppear().tap()
|
|
app.buttons["commitRuleButton"].waitToAppear().tap()
|
|
|
|
app.buttons["ruleCard-In the Zone"].waitToAppear().tap()
|
|
let row = app.element("detailRow-Adult websites").waitToAppear()
|
|
XCTAssertTrue(row.label.contains("Blocked"), "Got: \(row.label)")
|
|
}
|
|
|
|
func testAdultContentDefaultsToAllowed() throws {
|
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
|
app.goToRulesTab()
|
|
app.buttons["ruleCard-Work Time"].waitToAppear().tap()
|
|
let row = app.element("detailRow-Adult websites").waitToAppear()
|
|
XCTAssertTrue(row.label.contains("Allowed"), "Got: \(row.label)")
|
|
}
|
|
|
|
/// Block Adult Content is a Schedule-only option: the Time Limit editor must
|
|
/// not offer the toggle, and the rule's detail must not show the row.
|
|
func testTimeLimitOmitsAdultContent() throws {
|
|
let app = XCUIApplication.launchOpenAppLock()
|
|
app.goToRulesTab()
|
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
|
app.buttons["ruleKind-timeLimit"].waitToAppear().tap()
|
|
|
|
// Scroll to the bottom of the form, where Hard Mode lives. The adult
|
|
// toggle (which would sit beside it on a Schedule rule) is absent.
|
|
app.staticTexts["ruleEditorTitle"].waitToAppear()
|
|
app.swipeUp()
|
|
app.switches["hardModeToggle"].waitToAppear()
|
|
XCTAssertFalse(
|
|
app.switches["adultContentToggle"].exists,
|
|
"Time-limit rules must not offer Block Adult Content"
|
|
)
|
|
|
|
app.buttons["commitRuleButton"].waitToAppear().tap()
|
|
app.buttons["ruleCard-Time Keeper"].waitToAppear().tap()
|
|
app.element("detailRuleName").waitToAppear()
|
|
XCTAssertFalse(
|
|
app.element("detailRow-Adult websites").exists,
|
|
"Time-limit detail must not show the Adult websites row"
|
|
)
|
|
}
|
|
|
|
func testEditorSupportsNativeSwipeBack() throws {
|
|
let app = XCUIApplication.launchOpenAppLock()
|
|
app.goToRulesTab()
|
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
|
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
|
app.staticTexts["ruleEditorTitle"].waitToAppear()
|
|
|
|
// Native push navigation supports the edge-swipe back gesture.
|
|
let edge = app.coordinate(withNormalizedOffset: CGVector(dx: 0.02, dy: 0.5))
|
|
let middle = app.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.5))
|
|
edge.press(forDuration: 0.05, thenDragTo: middle)
|
|
|
|
app.buttons["ruleKind-schedule"].waitToAppear()
|
|
XCTAssertTrue(app.staticTexts["New Rule"].exists)
|
|
}
|
|
|
|
func testNewRuleSheetShowsTypesAndPresets() throws {
|
|
let app = XCUIApplication.launchOpenAppLock()
|
|
app.goToRulesTab()
|
|
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)
|
|
}
|
|
}
|