|
@@ -138,4 +138,111 @@ import Testing
|
|
|
])
|
|
])
|
|
|
#expect(store.canDelete(dayOnly))
|
|
#expect(store.canDelete(dayOnly))
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // MARK: - Per-tier snooze
|
|
|
|
|
+
|
|
|
|
|
+ /// Builds a unique UserDefaults suite (UUID), wipes its persistent domain,
|
|
|
|
|
+ /// and returns the defaults plus the derived keys — so a test can seed or
|
|
|
|
|
+ /// reload across multiple stores on the same backing store.
|
|
|
|
|
+ private static func makeSuite() -> (defaults: UserDefaults, configsKey: String, snoozesKey: String) {
|
|
|
|
|
+ let suiteName = "DeviceAlertsStoreTests.\(UUID().uuidString)"
|
|
|
|
|
+ let defaults = UserDefaults(suiteName: suiteName)!
|
|
|
|
|
+ defaults.removePersistentDomain(forName: suiteName)
|
|
|
|
|
+ let configsKey = "configs.\(suiteName)"
|
|
|
|
|
+ let snoozesKey = "snoozes.\(suiteName)"
|
|
|
|
|
+ return (defaults, configsKey, snoozesKey)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test("Snoozed tier is active before its expiry") func snoozeTimeSensitiveActiveBeforeExpiry() {
|
|
|
|
|
+ let now = Date()
|
|
|
|
|
+ let store = Self.makeStore()
|
|
|
|
|
+ store.snoozeTier(.timeSensitive, until: now.addingTimeInterval(600))
|
|
|
|
|
+ #expect(store.isTierSnoozed(.timeSensitive, at: now))
|
|
|
|
|
+ #expect(!store.isTierSnoozed(.timeSensitive, at: now.addingTimeInterval(601)))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test("Snoozing one tier doesn't affect another") func snoozeDoesNotAffectOtherTiers() {
|
|
|
|
|
+ let now = Date()
|
|
|
|
|
+ let store = Self.makeStore()
|
|
|
|
|
+ store.snoozeTier(.timeSensitive, until: now.addingTimeInterval(600))
|
|
|
|
|
+ #expect(!store.isTierSnoozed(.normal, at: now))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test("Snooze with a past until removes the key") func snoozeWithPastUntilRemoves() {
|
|
|
|
|
+ let now = Date()
|
|
|
|
|
+ let store = Self.makeStore()
|
|
|
|
|
+ store.snoozeTier(.timeSensitive, until: now.addingTimeInterval(-1))
|
|
|
|
|
+ #expect(!store.isTierSnoozed(.timeSensitive, at: now))
|
|
|
|
|
+ #expect(store.tierSnoozes["timeSensitive"] == nil)
|
|
|
|
|
+
|
|
|
|
|
+ // Live snooze, then a past until should remove the existing key.
|
|
|
|
|
+ store.snoozeTier(.timeSensitive, until: now.addingTimeInterval(600))
|
|
|
|
|
+ #expect(store.tierSnoozes["timeSensitive"] != nil)
|
|
|
|
|
+ store.snoozeTier(.timeSensitive, until: now.addingTimeInterval(-1))
|
|
|
|
|
+ #expect(store.tierSnoozes["timeSensitive"] == nil)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test("Snooze persists across a store reload") func snoozePersistsAcrossStoreReload() {
|
|
|
|
|
+ let now = Date()
|
|
|
|
|
+ let suite = Self.makeSuite()
|
|
|
|
|
+ let store1 = DeviceAlertsStore(
|
|
|
|
|
+ defaults: suite.defaults,
|
|
|
|
|
+ configsKey: suite.configsKey,
|
|
|
|
|
+ snoozesKey: suite.snoozesKey
|
|
|
|
|
+ )
|
|
|
|
|
+ store1.snoozeTier(.timeSensitive, until: now.addingTimeInterval(3600))
|
|
|
|
|
+ let store2 = DeviceAlertsStore(
|
|
|
|
|
+ defaults: suite.defaults,
|
|
|
|
|
+ configsKey: suite.configsKey,
|
|
|
|
|
+ snoozesKey: suite.snoozesKey
|
|
|
|
|
+ )
|
|
|
|
|
+ #expect(store2.isTierSnoozed(.timeSensitive, at: now))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test("Expired snooze is pruned on load") func expiredSnoozeIsPrunedOnLoad() {
|
|
|
|
|
+ let now = Date()
|
|
|
|
|
+ let suite = Self.makeSuite()
|
|
|
|
|
+ let expired: [String: Date] = ["timeSensitive": now.addingTimeInterval(-60)]
|
|
|
|
|
+ suite.defaults.set(try? JSONEncoder().encode(expired), forKey: suite.snoozesKey)
|
|
|
|
|
+ let store = DeviceAlertsStore(
|
|
|
|
|
+ defaults: suite.defaults,
|
|
|
|
|
+ configsKey: suite.configsKey,
|
|
|
|
|
+ snoozesKey: suite.snoozesKey
|
|
|
|
|
+ )
|
|
|
|
|
+ #expect(store.tierSnoozes.isEmpty)
|
|
|
|
|
+ #expect(!store.isTierSnoozed(.timeSensitive, at: now))
|
|
|
|
|
+
|
|
|
|
|
+ // Companion: seed both an expired and a future entry — only the future
|
|
|
|
|
+ // .normal entry should survive the load-time prune.
|
|
|
|
|
+ let suite2 = Self.makeSuite()
|
|
|
|
|
+ let mixed: [String: Date] = [
|
|
|
|
|
+ "timeSensitive": now.addingTimeInterval(-60),
|
|
|
|
|
+ "normal": now.addingTimeInterval(3600)
|
|
|
|
|
+ ]
|
|
|
|
|
+ suite2.defaults.set(try? JSONEncoder().encode(mixed), forKey: suite2.snoozesKey)
|
|
|
|
|
+ let store2 = DeviceAlertsStore(
|
|
|
|
|
+ defaults: suite2.defaults,
|
|
|
|
|
+ configsKey: suite2.configsKey,
|
|
|
|
|
+ snoozesKey: suite2.snoozesKey
|
|
|
|
|
+ )
|
|
|
|
|
+ #expect(store2.tierSnoozes["timeSensitive"] == nil)
|
|
|
|
|
+ #expect(store2.tierSnoozes["normal"] != nil)
|
|
|
|
|
+ #expect(store2.isTierSnoozed(.normal, at: now))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test("isTierSnoozed is false exactly at the until instant") func isTierSnoozedFalseExactlyAtUntilInstant() {
|
|
|
|
|
+ let now = Date()
|
|
|
|
|
+ let until = now.addingTimeInterval(600)
|
|
|
|
|
+ let store = Self.makeStore()
|
|
|
|
|
+ store.snoozeTier(.timeSensitive, until: until)
|
|
|
|
|
+ #expect(!store.isTierSnoozed(.timeSensitive, at: until))
|
|
|
|
|
+ #expect(store.isTierSnoozed(.timeSensitive, at: until.addingTimeInterval(-0.001)))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test("Store accepts a critical snooze") func storeAcceptsCriticalSnooze() {
|
|
|
|
|
+ let now = Date()
|
|
|
|
|
+ let store = Self.makeStore()
|
|
|
|
|
+ store.snoozeTier(.critical, until: now.addingTimeInterval(600))
|
|
|
|
|
+ #expect(store.isTierSnoozed(.critical, at: now))
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|