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. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,35 +5,45 @@
|
||||
|
||||
import XCTest
|
||||
|
||||
/// The Usage section under Blocked Apps — seeded with limit rules at various
|
||||
/// The Usage section on the Home tab — seeded with limit rules at various
|
||||
/// budget states ("Time Keeper" 18m/45m, "Gate Keeper" 2/5 opens,
|
||||
/// "Doom Scroll" spent → blocked).
|
||||
/// "Doom Scroll" spent → moved to Currently Blocking).
|
||||
final class UsageUITests: XCTestCase {
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
func testUsageSectionShowsTimeAndOpenBudgets() throws {
|
||||
func testUsageSectionShowsTypeAndBudgets() throws {
|
||||
let app = XCUIApplication.launchOpenAppLock(seedScenario: "limits")
|
||||
|
||||
XCTAssertTrue(app.staticTexts["Usage"].waitToAppear().exists)
|
||||
|
||||
// The row leads with the rule type, then the live usage and remaining.
|
||||
let timeRow = app.element("usageRow-Time Keeper").waitToAppear()
|
||||
XCTAssertTrue(timeRow.label.contains("Time Limit"), "Got: \(timeRow.label)")
|
||||
XCTAssertTrue(timeRow.label.contains("18m of 45m used today"), "Got: \(timeRow.label)")
|
||||
XCTAssertTrue(timeRow.label.contains("27m left"), "Got: \(timeRow.label)")
|
||||
|
||||
let openRow = app.element("usageRow-Gate Keeper").waitToAppear()
|
||||
XCTAssertTrue(openRow.label.contains("Open Limit"), "Got: \(openRow.label)")
|
||||
XCTAssertTrue(openRow.label.contains("2 of 5 opens today"), "Got: \(openRow.label)")
|
||||
XCTAssertTrue(openRow.label.contains("3 opens left"), "Got: \(openRow.label)")
|
||||
}
|
||||
|
||||
func testSpentBudgetShowsAsBlockedUntilTomorrow() throws {
|
||||
func testSpentBudgetMovesToCurrentlyBlocking() throws {
|
||||
let app = XCUIApplication.launchOpenAppLock(seedScenario: "limits")
|
||||
|
||||
let spentRow = app.element("usageRow-Doom Scroll").waitToAppear()
|
||||
XCTAssertTrue(spentRow.label.contains("Blocked until tomorrow"), "Got: \(spentRow.label)")
|
||||
// A spent budget is a real block: the rule surfaces in Blocked Apps.
|
||||
XCTAssertTrue(app.buttons["blockedTile-Doom Scroll"].waitToAppear().exists)
|
||||
// A spent budget is a real block: the rule moves out of Usage and into
|
||||
// Currently Blocking, carrying its type + usage tracking.
|
||||
let tile = app.buttons["blockedTile-Doom Scroll"].waitToAppear()
|
||||
XCTAssertTrue(tile.label.contains("Time Limit"), "Got: \(tile.label)")
|
||||
XCTAssertTrue(tile.label.contains("30m of 30m used today"), "Got: \(tile.label)")
|
||||
|
||||
// It is no longer tracked under Usage.
|
||||
XCTAssertFalse(
|
||||
app.element("usageRow-Doom Scroll").exists,
|
||||
"A spent rule should leave the Usage section for Currently Blocking"
|
||||
)
|
||||
}
|
||||
|
||||
func testSpentBudgetCanBeUnblockedUntilTomorrow() throws {
|
||||
@@ -42,6 +52,7 @@ final class UsageUITests: XCTestCase {
|
||||
app.buttons["blockedTile-Doom Scroll"].waitToAppear().tap()
|
||||
app.sheets.buttons["Unblock"].waitToAppear().tap()
|
||||
|
||||
// Unblocked → paused (not blocking), so it drops back into Usage.
|
||||
app.staticTexts["nothingBlockedLabel"].waitToAppear()
|
||||
let row = app.element("usageRow-Doom Scroll").waitToAppear()
|
||||
XCTAssertTrue(row.label.contains("Unblocked until tomorrow"), "Got: \(row.label)")
|
||||
|
||||
Reference in New Issue
Block a user