Files
OpenAppLock/OpenAppLockUITests/AppListUITests.swift
Brendan Chen 93f2eceb3a feat: introduce reusable app lists for rules
- AppList @Model with launch migration from legacy inline selections
  (rules with identical selections share one list; idempotent)
- rules point at one app list; Block/Allow Only belongs to the rule and
  is offered only in the Schedule editor (limit kinds sanitize to Block)
- app-list picker sheet (select / create / edit / delete with in-use
  protection) replaces the per-rule selection sheet
- Hard Mode locks app-list editing while any hard rule is blocking
- SwiftData stability: relationships are only wired between managed
  models, and unit tests share one in-memory container (fresh context +
  data wipe per test) — per-test container creation trapped
  intermittently inside SwiftData
2026-06-12 20:02:35 -04:00

107 lines
4.2 KiB
Swift

//
// AppListUITests.swift
// OpenAppLockUITests
//
import XCTest
/// App lists: the editor's App List row, the picker (select / create / edit),
/// rule-level Block vs Allow Only, and the Hard Mode list lockdown.
final class AppListUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}
func testScheduleEditorOffersModeChoice() throws {
let app = XCUIApplication.launchOpenAppLock()
app.buttons["newRuleButton"].waitToAppear().tap()
app.buttons["ruleKind-schedule"].waitToAppear().tap()
app.staticTexts["ruleEditorTitle"].waitToAppear()
app.swipeUp()
app.element("selectionModePicker").waitToAppear()
XCTAssertTrue(app.staticTexts["Apps are blocked"].exists)
}
func testLimitEditorsAreBlockOnly() throws {
let app = XCUIApplication.launchOpenAppLock()
app.buttons["newRuleButton"].waitToAppear().tap()
app.buttons["ruleKind-timeLimit"].waitToAppear().tap()
app.element("selectedAppsRow").waitToAppear()
XCTAssertFalse(
app.element("selectionModePicker").exists,
"Time-limit rules are always Block; the mode picker must not appear"
)
// Back to the rule-type list, then check the open-limit editor too.
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-openLimit"].waitToAppear().tap()
app.element("selectedAppsRow").waitToAppear()
XCTAssertFalse(app.element("selectionModePicker").exists)
}
func testCreateAppListFlowSelectsNewList() throws {
let app = XCUIApplication.launchOpenAppLock()
app.buttons["newRuleButton"].waitToAppear().tap()
app.buttons["ruleKind-timeLimit"].waitToAppear().tap()
app.element("selectedAppsRow").waitToAppear().tap()
// Fresh install: no lists yet, so the picker offers creation.
app.element("emptyAppListsLabel").waitToAppear()
app.buttons["newAppListButton"].tap()
let nameField = app.textFields["appListNameField"].waitToAppear()
nameField.tap()
nameField.typeText("Focus Apps\n")
app.buttons["saveAppListButton"].waitToAppear().tap()
// Saving pops back to the picker with the new list selected.
app.element("appListRow-Focus Apps").waitToAppear()
app.buttons["closeAppListPickerButton"].tap()
// The editor row now reports the chosen list.
let row = app.element("selectedAppsRow").waitToAppear()
XCTAssertTrue(row.label.contains("Focus Apps"), "Got: \(row.label)")
}
func testDetailShowsAppListName() throws {
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
app.buttons["ruleCard-Work Time"].waitToAppear().tap()
let row = app.element("detailRow-Block").waitToAppear()
XCTAssertTrue(row.label.contains("Distractions"), "Got: \(row.label)")
}
func testHardModeSessionLocksAppListEditing() throws {
let app = XCUIApplication.launchOpenAppLock(seedScenario: "hard-mode-active")
// "Sleep" is soft and editable even while "Locked In" hard-blocks.
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
app.buttons["editRuleButton"].waitToAppear().tap()
app.element("selectedAppsRow").waitToAppear().tap()
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"
)
}
func testAppListsEditableWithoutHardSession() throws {
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
app.buttons["editRuleButton"].waitToAppear().tap()
app.element("selectedAppsRow").waitToAppear().tap()
app.buttons["editAppListButton-Distractions"].waitToAppear()
XCTAssertFalse(app.element("appListsLockedNotice").exists)
}
}