feat: track limit budgets in a usage ledger and surface them in a Usage section
- RuleUsage + UsageLedger: per-rule, per-day minutes/opens in app-group defaults (monotonic minutes, incrementing opens, midnight rollover by day-keying); MockUsageLedger for tests and seeded UI scenarios - usage-aware status: a limit rule whose daily budget is spent on an enabled day is active (blocking) until next midnight; Hard Mode gating and app-list locking honor it; soft unblock pauses until midnight - RuleEnforcer shields spent limit rules (always Block mode) while the app runs - new Usage section under Blocked Apps: '18m of 45m used today · 27m left', '2 of 5 opens today · 3 opens left', 'Blocked until tomorrow' - new 'limits' seed scenario + UI tests
This commit is contained in:
49
OpenAppLockUITests/UsageUITests.swift
Normal file
49
OpenAppLockUITests/UsageUITests.swift
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// UsageUITests.swift
|
||||
// OpenAppLockUITests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
/// The Usage section under Blocked Apps — seeded with limit rules at various
|
||||
/// budget states ("Time Keeper" 18m/45m, "Gate Keeper" 2/5 opens,
|
||||
/// "Doom Scroll" spent → blocked).
|
||||
final class UsageUITests: XCTestCase {
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
func testUsageSectionShowsTimeAndOpenBudgets() throws {
|
||||
let app = XCUIApplication.launchOpenAppLock(seedScenario: "limits")
|
||||
|
||||
XCTAssertTrue(app.staticTexts["Usage"].waitToAppear().exists)
|
||||
|
||||
let timeRow = app.element("usageRow-Time Keeper").waitToAppear()
|
||||
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("2 of 5 opens today"), "Got: \(openRow.label)")
|
||||
XCTAssertTrue(openRow.label.contains("3 opens left"), "Got: \(openRow.label)")
|
||||
}
|
||||
|
||||
func testSpentBudgetShowsAsBlockedUntilTomorrow() 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)
|
||||
}
|
||||
|
||||
func testSpentBudgetCanBeUnblockedUntilTomorrow() throws {
|
||||
let app = XCUIApplication.launchOpenAppLock(seedScenario: "limits")
|
||||
|
||||
app.buttons["blockedTile-Doom Scroll"].waitToAppear().tap()
|
||||
app.sheets.buttons["Unblock"].waitToAppear().tap()
|
||||
|
||||
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