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.
147 lines
5.9 KiB
Swift
147 lines
5.9 KiB
Swift
//
|
|
// RuleManagementUITests.swift
|
|
// OpenAppLockUITests
|
|
//
|
|
|
|
import XCTest
|
|
|
|
/// Detail sheet, editing, disabling, deleting, and unblocking — seeded with
|
|
/// one actively-blocking rule ("Work Time") and one upcoming rule ("Sleep").
|
|
/// Rule cards live on the Rules tab; blocked tiles on the Home tab.
|
|
final class RuleManagementUITests: XCTestCase {
|
|
override func setUpWithError() throws {
|
|
continueAfterFailure = false
|
|
}
|
|
|
|
func testDetailShowsLiveStatusAndFacts() throws {
|
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
|
app.goToRulesTab()
|
|
|
|
app.buttons["ruleCard-Work Time"].waitToAppear().tap()
|
|
|
|
XCTAssertEqual(app.staticTexts["detailRuleName"].waitToAppear().label, "Work Time")
|
|
XCTAssertTrue(app.staticTexts["detailStatusLabel"].label.contains("left"))
|
|
app.element("detailRow-During this time").waitToAppear()
|
|
XCTAssertTrue(app.element("detailRow-On these days").exists)
|
|
XCTAssertTrue(app.element("detailRow-Unblocks allowed").exists)
|
|
app.buttons["editRuleButton"].waitToAppear()
|
|
|
|
app.buttons["closeDetailButton"].tap()
|
|
app.buttons["newRuleButton"].waitToAppear()
|
|
}
|
|
|
|
func testEditRuleTogglesHardModeOn() throws {
|
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
|
app.goToRulesTab()
|
|
|
|
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
|
|
app.buttons["editRuleButton"].waitToAppear().tap()
|
|
|
|
app.switches["hardModeToggle"].waitToAppear().tap()
|
|
app.buttons["doneButton"].tap()
|
|
|
|
// Back on the detail view, unblocks are no longer allowed.
|
|
let row = app.element("detailRow-Unblocks allowed").waitToAppear()
|
|
XCTAssertTrue(row.label.contains("No"), "Expected 'Unblocks allowed: No', got: \(row.label)")
|
|
}
|
|
|
|
func testDisableRule() throws {
|
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
|
app.goToRulesTab()
|
|
|
|
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
|
|
app.buttons["editRuleButton"].waitToAppear().tap()
|
|
|
|
// Disable lives in the ellipsis menu in the navigation bar.
|
|
let actionsMenu = app.navigationBars.buttons["ruleActionsMenu"].waitToAppear()
|
|
XCTAssertEqual(actionsMenu.label, "Rule Actions")
|
|
actionsMenu.tap()
|
|
app.buttons["Disable Rule"].waitToAppear().tap()
|
|
|
|
// The detail caption now reports the rule as disabled.
|
|
let status = app.staticTexts["detailStatusLabel"].waitToAppear()
|
|
XCTAssertTrue(status.label.contains("Disabled"), "Got: \(status.label)")
|
|
|
|
app.buttons["closeDetailButton"].tap()
|
|
let cardStatus = app.staticTexts["ruleStatus-Sleep"].waitToAppear()
|
|
XCTAssertEqual(cardStatus.label, "Disabled")
|
|
}
|
|
|
|
func testDeleteRuleRemovesCard() throws {
|
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
|
app.goToRulesTab()
|
|
|
|
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
|
|
app.buttons["editRuleButton"].waitToAppear().tap()
|
|
|
|
// Delete lives in the ellipsis menu in the navigation bar.
|
|
app.navigationBars.buttons["ruleActionsMenu"].waitToAppear().tap()
|
|
app.buttons["Delete Rule"].waitToAppear().tap()
|
|
|
|
app.buttons["newRuleButton"].waitToAppear()
|
|
XCTAssertFalse(
|
|
app.buttons["ruleCard-Sleep"].waitForExistence(timeout: 2),
|
|
"Deleted rule's card should disappear"
|
|
)
|
|
XCTAssertTrue(app.buttons["ruleCard-Work Time"].exists)
|
|
}
|
|
|
|
func testUnblockActiveSoftRule() throws {
|
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
|
|
|
// The active rule surfaces in Currently Blocking (Home tab); unblocking pauses it.
|
|
app.buttons["blockedTile-Work Time"].waitToAppear().tap()
|
|
app.sheets.buttons["Unblock"].waitToAppear().tap()
|
|
|
|
app.staticTexts["nothingBlockedLabel"].waitToAppear()
|
|
// The paused state shows on the rule's card over on the Rules tab.
|
|
app.goToRulesTab()
|
|
XCTAssertEqual(app.staticTexts["ruleStatus-Work Time"].waitToAppear().label, "Paused")
|
|
}
|
|
}
|
|
|
|
/// Hard block behavior — seeded with an actively-blocking Hard Mode rule.
|
|
final class HardModeUITests: XCTestCase {
|
|
override func setUpWithError() throws {
|
|
continueAfterFailure = false
|
|
}
|
|
|
|
func testHardLockedRuleCannotBeEdited() throws {
|
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "hard-mode-active")
|
|
app.goToRulesTab()
|
|
|
|
app.buttons["ruleCard-Locked In"].waitToAppear().tap()
|
|
|
|
// The lock notice replaces Edit Rule entirely.
|
|
app.element("hardModeLockedNotice").waitToAppear()
|
|
XCTAssertFalse(app.buttons["editRuleButton"].exists)
|
|
XCTAssertTrue(app.element("detailRow-Unblocks allowed").label.contains("No"))
|
|
}
|
|
|
|
func testHardLockedRuleCannotBeUnblocked() throws {
|
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "hard-mode-active")
|
|
|
|
// The hard rule shows a lock (not an Unblock button) in Currently Blocking;
|
|
// tapping the row still explains why it can't be lifted.
|
|
app.buttons["blockedTile-Locked In"].waitToAppear().tap()
|
|
|
|
// No unblock dialog — just the refusal alert.
|
|
let alert = app.alerts["Hard Mode is on"].waitToAppear()
|
|
XCTAssertTrue(alert.staticTexts["This block can't be lifted until it ends."].exists)
|
|
alert.buttons["OK"].tap()
|
|
|
|
// Still blocked.
|
|
XCTAssertTrue(app.buttons["blockedTile-Locked In"].exists)
|
|
XCTAssertFalse(app.staticTexts["nothingBlockedLabel"].exists)
|
|
}
|
|
|
|
func testSoftRuleUnblockOfferedButHardRuleRefused() throws {
|
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
|
|
|
app.buttons["blockedTile-Work Time"].waitToAppear().tap()
|
|
// Soft rule: the confirmation dialog appears instead of the refusal alert.
|
|
app.sheets.buttons["Unblock"].waitToAppear()
|
|
XCTAssertFalse(app.alerts["Hard Mode is on"].exists)
|
|
}
|
|
}
|