Files
OpenAppLock/SeveredUITests/RuleCreationUITests.swift
Brendan Chen fd1f5d758f feat: adult content filter toggle + native New Rule navigation
- Add Block Adult Content toggle to all three rule editors, persisted as
  BlockingRule.blockAdultContent (inline default for clean migration of
  existing stores) and surfaced in the detail sheet as an
  'Adult websites: Blocked/Allowed' row
- Engage Screen Time's adult-website filter
  (webContent.blockedByFilter = .auto()) alongside the rule's shield and
  clear it when the shield clears
- Replace the New Rule sheet's view-swap with a NavigationStack push
  (navigationDestination(item:)); the editor uses native chrome there
  (system back button, inline title, toolbar rename), enabling the push
  animation and edge-swipe back
- Tests: 93 passing (+2 unit: draft round-trip and enforcer forwarding;
  +3 UI: toggle-to-detail flow, default-allowed row, swipe-back as a
  behavioral proof of native navigation)
- Spec updated accordingly (editor sections, behavior, data model,
  navigation note)
2026-06-12 13:34:52 -04:00

135 lines
5.6 KiB
Swift

//
// RuleCreationUITests.swift
// SeveredUITests
//
import XCTest
final class RuleCreationUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}
func testCreateScheduleRuleFromTypeCard() throws {
let app = XCUIApplication.launchSevered()
app.element("emptyRulesCard").waitToAppear()
app.buttons["newRuleButton"].tap()
app.staticTexts["New Rule"].waitToAppear()
app.buttons["ruleKind-schedule"].waitToAppear().tap()
// Editor opens with the schedule defaults.
XCTAssertEqual(app.staticTexts["ruleEditorTitle"].waitToAppear().label, "In the Zone")
XCTAssertTrue(app.staticTexts["During this time"].exists)
// Hold to commit saves the rule and returns home.
app.buttons["holdToCommitButton"].waitToAppear().press(forDuration: 2.0)
app.buttons["ruleCard-In the Zone"].waitToAppear()
XCTAssertFalse(app.element("emptyRulesCard").exists)
}
func testCreateRuleFromPreset() throws {
let app = XCUIApplication.launchSevered()
app.buttons["newRuleButton"].waitToAppear().tap()
app.buttons["preset-work-time"].waitToAppear().tap()
XCTAssertEqual(app.staticTexts["ruleEditorTitle"].waitToAppear().label, "Work Time")
app.buttons["holdToCommitButton"].waitToAppear().press(forDuration: 2.0)
app.buttons["ruleCard-Work Time"].waitToAppear()
}
func testRenameRuleInEditor() throws {
let app = XCUIApplication.launchSevered()
app.buttons["newRuleButton"].waitToAppear().tap()
app.buttons["ruleKind-schedule"].waitToAppear().tap()
app.buttons["renameButton"].waitToAppear().tap()
let nameField = app.textFields.firstMatch.waitToAppear()
nameField.tap()
// Clear the prefilled name, then type the new one.
let deletions = String(repeating: XCUIKeyboardKey.delete.rawValue, count: 24)
nameField.typeText(deletions + "My Focus")
app.buttons["OK"].tap()
XCTAssertEqual(app.staticTexts["ruleEditorTitle"].label, "My Focus")
app.buttons["holdToCommitButton"].waitToAppear().press(forDuration: 2.0)
app.buttons["ruleCard-My Focus"].waitToAppear()
}
func testDayTogglesUpdateSummary() throws {
let app = XCUIApplication.launchSevered()
app.buttons["newRuleButton"].waitToAppear().tap()
app.buttons["ruleKind-schedule"].waitToAppear().tap()
// Default is Weekdays; enabling Saturday and Sunday makes it Every day.
XCTAssertTrue(app.staticTexts["Weekdays"].waitToAppear().exists)
app.buttons["dayToggle-1"].tap()
app.buttons["dayToggle-7"].tap()
XCTAssertTrue(app.staticTexts["Every day"].waitToAppear().exists)
}
func testCreateTimeLimitRule() throws {
let app = XCUIApplication.launchSevered()
app.buttons["newRuleButton"].waitToAppear().tap()
app.buttons["ruleKind-timeLimit"].waitToAppear().tap()
XCTAssertEqual(app.staticTexts["ruleEditorTitle"].waitToAppear().label, "Time Keeper")
XCTAssertTrue(app.staticTexts["When I use"].exists)
XCTAssertEqual(app.staticTexts["dailyLimitStepperValue"].label, "45m")
app.steppers["dailyLimitStepper"].buttons["Increment"].tap()
XCTAssertEqual(app.staticTexts["dailyLimitStepperValue"].label, "60m")
app.buttons["holdToCommitButton"].waitToAppear().press(forDuration: 2.0)
app.buttons["ruleCard-Time Keeper"].waitToAppear()
}
func testAdultContentToggleFlowsToDetail() throws {
let app = XCUIApplication.launchSevered()
app.buttons["newRuleButton"].waitToAppear().tap()
app.buttons["ruleKind-schedule"].waitToAppear().tap()
app.switches["adultContentToggle"].waitToAppear().tap()
app.buttons["holdToCommitButton"].waitToAppear().press(forDuration: 2.0)
app.buttons["ruleCard-In the Zone"].waitToAppear().tap()
let row = app.element("detailRow-Adult websites").waitToAppear()
XCTAssertTrue(row.label.contains("Blocked"), "Got: \(row.label)")
}
func testAdultContentDefaultsToAllowed() throws {
let app = XCUIApplication.launchSevered(seedScenario: "standard")
app.buttons["ruleCard-Work Time"].waitToAppear().tap()
let row = app.element("detailRow-Adult websites").waitToAppear()
XCTAssertTrue(row.label.contains("Allowed"), "Got: \(row.label)")
}
func testEditorSupportsNativeSwipeBack() throws {
let app = XCUIApplication.launchSevered()
app.buttons["newRuleButton"].waitToAppear().tap()
app.buttons["ruleKind-schedule"].waitToAppear().tap()
app.staticTexts["ruleEditorTitle"].waitToAppear()
// Native push navigation supports the edge-swipe back gesture.
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-schedule"].waitToAppear()
XCTAssertTrue(app.staticTexts["New Rule"].exists)
}
func testNewRuleSheetShowsTypesAndPresets() throws {
let app = XCUIApplication.launchSevered()
app.buttons["newRuleButton"].waitToAppear().tap()
app.buttons["ruleKind-schedule"].waitToAppear()
XCTAssertTrue(app.buttons["ruleKind-timeLimit"].exists)
XCTAssertTrue(app.buttons["ruleKind-openLimit"].exists)
XCTAssertTrue(app.staticTexts["Get More Done"].exists)
XCTAssertTrue(app.buttons["preset-work-time"].exists)
XCTAssertTrue(app.buttons["preset-laser-focus"].exists)
}
}