feat: re-skin to native three-tab UI and add Uninstall Protection
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.
This commit is contained in:
67
OpenAppLockUITests/SettingsUITests.swift
Normal file
67
OpenAppLockUITests/SettingsUITests.swift
Normal file
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// SettingsUITests.swift
|
||||
// OpenAppLockUITests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
/// The Settings tab: the Uninstall Protection toggle and the Manage App Lists
|
||||
/// flow (which reuses the rule editor's app-list library, minus selection).
|
||||
final class SettingsUITests: XCTestCase {
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
func testUninstallProtectionToggleStartsOffAndFlips() throws {
|
||||
let app = XCUIApplication.launchOpenAppLock()
|
||||
app.goToSettingsTab()
|
||||
|
||||
let toggle = app.switches["uninstallProtectionToggle"].waitToAppear()
|
||||
XCTAssertEqual(toggle.value as? String, "0", "Uninstall Protection should default off")
|
||||
// `.tap()` lands on the element's center (over the label) and doesn't
|
||||
// reliably flip a SwiftUI switch — tap the control itself.
|
||||
toggle.coordinate(withNormalizedOffset: CGVector(dx: 0.92, dy: 0.5)).tap()
|
||||
XCTAssertEqual(toggle.value as? String, "1", "Tapping should turn it on")
|
||||
}
|
||||
|
||||
func testManageAppListsCreateFlow() throws {
|
||||
let app = XCUIApplication.launchOpenAppLock()
|
||||
app.goToSettingsTab()
|
||||
|
||||
app.element("manageAppListsButton").waitToAppear().tap()
|
||||
|
||||
// Fresh install: no lists yet — the same create flow as the rule editor.
|
||||
app.element("emptyAppListsLabel").waitToAppear()
|
||||
app.buttons["newAppListButton"].tap()
|
||||
|
||||
let nameField = app.textFields["appListNameField"].waitToAppear()
|
||||
nameField.tap()
|
||||
nameField.typeText("Distractions\n")
|
||||
|
||||
app.element("emptySelectionLabel").waitToAppear()
|
||||
app.buttons["editAppsButton"].tap()
|
||||
app.element("selectionCountLabel").waitToAppear()
|
||||
app.buttons["confirmSelectionButton"].tap()
|
||||
|
||||
app.buttons["saveAppListButton"].waitToAppear().tap()
|
||||
|
||||
// Saving returns to the management list with the new list present.
|
||||
app.element("appListRow-Distractions").waitToAppear()
|
||||
}
|
||||
|
||||
func testManageAppListsLockedDuringHardSession() throws {
|
||||
let app = XCUIApplication.launchOpenAppLock(seedScenario: "hard-mode-active")
|
||||
app.goToSettingsTab()
|
||||
|
||||
app.element("manageAppListsButton").waitToAppear().tap()
|
||||
|
||||
// The seeded "Distractions" list is visible but read-only while the
|
||||
// hard-mode rule is blocking — same lock as the rule editor's picker.
|
||||
app.element("appListRow-Distractions").waitToAppear()
|
||||
app.element("appListsLockedNotice").waitToAppear()
|
||||
XCTAssertFalse(
|
||||
app.buttons["editAppListButton-Distractions"].exists,
|
||||
"App lists must be read-only while a Hard Mode rule is blocking"
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user