TrioModalAlertSchedulerTests.swift 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import Foundation
  2. import LoopKit
  3. import Testing
  4. @testable import Trio
  5. @MainActor
  6. @Suite("Trio Alerts: TrioModalAlertScheduler clearNonCriticalBanners") struct TrioModalAlertSchedulerClearTests {
  7. private func makeAlert(
  8. interruptionLevel: LoopKit.Alert.InterruptionLevel,
  9. identifier: String
  10. ) -> LoopKit.Alert {
  11. let content = LoopKit.Alert.Content(
  12. title: "Title",
  13. body: "Body",
  14. acknowledgeActionButtonLabel: "OK"
  15. )
  16. return LoopKit.Alert(
  17. identifier: LoopKit.Alert.Identifier(
  18. managerIdentifier: "trio.test",
  19. alertIdentifier: identifier
  20. ),
  21. foregroundContent: content,
  22. backgroundContent: content,
  23. trigger: .immediate,
  24. interruptionLevel: interruptionLevel,
  25. sound: nil
  26. )
  27. }
  28. @Test("Mixed queue keeps only critical") func mixedKeepsOnlyCritical() {
  29. let scheduler = TrioModalAlertScheduler()
  30. scheduler.seedForTesting([
  31. makeAlert(interruptionLevel: .critical, identifier: "c"),
  32. makeAlert(interruptionLevel: .timeSensitive, identifier: "ts"),
  33. makeAlert(interruptionLevel: .active, identifier: "a")
  34. ])
  35. scheduler.clearNonCriticalBanners()
  36. #expect(scheduler.active.map(\.identifier.alertIdentifier) == ["c"])
  37. }
  38. @Test("All-critical queue is unchanged and order-preserved") func allCriticalUnchanged() {
  39. let scheduler = TrioModalAlertScheduler()
  40. scheduler.seedForTesting([
  41. makeAlert(interruptionLevel: .critical, identifier: "c1"),
  42. makeAlert(interruptionLevel: .critical, identifier: "c2")
  43. ])
  44. scheduler.clearNonCriticalBanners()
  45. #expect(scheduler.active.map(\.identifier.alertIdentifier) == ["c1", "c2"])
  46. }
  47. @Test("Empty queue stays empty") func emptyStaysEmpty() {
  48. let scheduler = TrioModalAlertScheduler()
  49. scheduler.seedForTesting([])
  50. scheduler.clearNonCriticalBanners()
  51. #expect(scheduler.active.isEmpty)
  52. }
  53. @Test("All-non-critical queue becomes empty") func allNonCriticalBecomesEmpty() {
  54. let scheduler = TrioModalAlertScheduler()
  55. scheduler.seedForTesting([
  56. makeAlert(interruptionLevel: .timeSensitive, identifier: "ts"),
  57. makeAlert(interruptionLevel: .active, identifier: "a")
  58. ])
  59. scheduler.clearNonCriticalBanners()
  60. #expect(scheduler.active.isEmpty)
  61. }
  62. }
  63. @Suite("Trio Alerts: TrioModalAlertScheduler fire-gate") struct TrioModalAlertSchedulerFireGateTests {
  64. @Test("Critical, active, snoozed -> insert") func criticalActiveSnoozedInserts() {
  65. #expect(
  66. TrioModalAlertScheduler.shouldInsertOnFire(
  67. interruptionLevel: .critical,
  68. isAlertActive: true,
  69. isSnoozeActive: true
  70. ) == .insert
  71. )
  72. }
  73. @Test("TimeSensitive, active, snoozed -> suppressKeepPending") func timeSensitiveActiveSnoozedSuppresses() {
  74. #expect(
  75. TrioModalAlertScheduler.shouldInsertOnFire(
  76. interruptionLevel: .timeSensitive,
  77. isAlertActive: true,
  78. isSnoozeActive: true
  79. ) == .suppressKeepPending
  80. )
  81. }
  82. @Test("TimeSensitive, active, not snoozed -> insert") func timeSensitiveActiveNotSnoozedInserts() {
  83. #expect(
  84. TrioModalAlertScheduler.shouldInsertOnFire(
  85. interruptionLevel: .timeSensitive,
  86. isAlertActive: true,
  87. isSnoozeActive: false
  88. ) == .insert
  89. )
  90. }
  91. @Test("Active, active, snoozed -> suppressKeepPending") func activeActiveSnoozedSuppresses() {
  92. #expect(
  93. TrioModalAlertScheduler.shouldInsertOnFire(
  94. interruptionLevel: .active,
  95. isAlertActive: true,
  96. isSnoozeActive: true
  97. ) == .suppressKeepPending
  98. )
  99. }
  100. @Test("TimeSensitive, inactive, not snoozed -> dropStale") func timeSensitiveInactiveDropsStale() {
  101. #expect(
  102. TrioModalAlertScheduler.shouldInsertOnFire(
  103. interruptionLevel: .timeSensitive,
  104. isAlertActive: false,
  105. isSnoozeActive: false
  106. ) == .dropStale
  107. )
  108. }
  109. @Test("Critical, inactive, snoozed -> dropStale") func criticalInactiveDropsStale() {
  110. #expect(
  111. TrioModalAlertScheduler.shouldInsertOnFire(
  112. interruptionLevel: .critical,
  113. isAlertActive: false,
  114. isSnoozeActive: true
  115. ) == .dropStale
  116. )
  117. }
  118. @Test("Active, inactive, snoozed -> dropStale") func activeInactiveDropsStale() {
  119. #expect(
  120. TrioModalAlertScheduler.shouldInsertOnFire(
  121. interruptionLevel: .active,
  122. isAlertActive: false,
  123. isSnoozeActive: true
  124. ) == .dropStale
  125. )
  126. }
  127. // nil-responder modeled as isAlertActive defaulting true and
  128. // isSnoozeActive defaulting false (== true comparison yields false).
  129. @Test("Nil responder, timeSensitive -> insert") func nilResponderTimeSensitiveInserts() {
  130. #expect(
  131. TrioModalAlertScheduler.shouldInsertOnFire(
  132. interruptionLevel: .timeSensitive,
  133. isAlertActive: true,
  134. isSnoozeActive: false
  135. ) == .insert
  136. )
  137. }
  138. @Test("Nil responder, critical -> insert") func nilResponderCriticalInserts() {
  139. #expect(
  140. TrioModalAlertScheduler.shouldInsertOnFire(
  141. interruptionLevel: .critical,
  142. isAlertActive: true,
  143. isSnoozeActive: false
  144. ) == .insert
  145. )
  146. }
  147. }