refactor: model rule options as a per-kind sum type
Replace the wide BlockingRule/RuleDraft "god struct" — where every option existed for every kind — with a RuleConfiguration sum type that carries only the options each kind actually has: .schedule(ScheduleConfig: window + selectionMode + blockAdultContent) .timeLimit(TimeLimitConfig: dailyLimitMinutes) .openLimit(OpenLimitConfig: maxOpens) Name, days, Hard Mode, app list, and pause stay common to all kinds. This makes illegal states unrepresentable: Block / Allow Only and Block Adult Content are now structurally Schedule-only. User-visible behavior change: the Time Limit and Open Limit editors no longer offer a Block Adult Content toggle, and their detail sheets drop the "Adult websites" row — those never made sense for a usage budget. Limit rules are always Block and never engage the web-content filter. BlockingRule keeps flat columns as raw persistence behind a computed `configuration` bridge (lowest SwiftData risk; the cross-process RuleSnapshot wire format and the Screen Time extensions are untouched). Logic, the editors, and the detail sheet all switch on the sum type. Spec (§1, §3.5/§3.6, §5.2) updated first; tests reworked to the new API with new structural-guarantee unit tests and a UI test asserting the Time Limit editor/detail omit adult content. Full suite green (180 tests). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
44
OpenAppLockTests/RuleConfigurationTests.swift
Normal file
44
OpenAppLockTests/RuleConfigurationTests.swift
Normal file
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// RuleConfigurationTests.swift
|
||||
// OpenAppLockTests
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
@testable import OpenAppLock
|
||||
|
||||
@MainActor
|
||||
@Suite("RuleConfiguration sum type")
|
||||
struct RuleConfigurationTests {
|
||||
@Test("Each case reports its kind")
|
||||
func kindDerivation() {
|
||||
#expect(RuleConfiguration.schedule(ScheduleConfig()).kind == .schedule)
|
||||
#expect(RuleConfiguration.timeLimit(TimeLimitConfig()).kind == .timeLimit)
|
||||
#expect(RuleConfiguration.openLimit(OpenLimitConfig()).kind == .openLimit)
|
||||
}
|
||||
|
||||
@Test("Defaults match the reference app's new-rule defaults")
|
||||
func defaults() {
|
||||
let schedule = RuleConfiguration.default(for: .schedule).scheduleConfig
|
||||
#expect(schedule?.startMinutes == 9 * 60)
|
||||
#expect(schedule?.endMinutes == 17 * 60)
|
||||
#expect(schedule?.selectionMode == .block)
|
||||
#expect(schedule?.blockAdultContent == false)
|
||||
|
||||
#expect(RuleConfiguration.default(for: .timeLimit).timeLimitConfig?.dailyLimitMinutes == 45)
|
||||
#expect(RuleConfiguration.default(for: .openLimit).openLimitConfig?.maxOpens == 5)
|
||||
}
|
||||
|
||||
@Test("Typed projections only unwrap the matching case")
|
||||
func projections() {
|
||||
let schedule = RuleConfiguration.schedule(ScheduleConfig(blockAdultContent: true))
|
||||
#expect(schedule.scheduleConfig?.blockAdultContent == true)
|
||||
#expect(schedule.timeLimitConfig == nil)
|
||||
#expect(schedule.openLimitConfig == nil)
|
||||
|
||||
let openLimit = RuleConfiguration.openLimit(OpenLimitConfig(maxOpens: 7))
|
||||
#expect(openLimit.openLimitConfig?.maxOpens == 7)
|
||||
#expect(openLimit.scheduleConfig == nil)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user