@@ -96,13 +96,80 @@ public func confirmation<R>(
96
96
expectedCount: Int = 1 ,
97
97
sourceLocation: SourceLocation = #_sourceLocation,
98
98
_ body: ( Confirmation ) async throws -> R
99
+ ) async rethrows -> R {
100
+ try await confirmation (
101
+ comment,
102
+ expectedCount: expectedCount ... expectedCount,
103
+ sourceLocation: sourceLocation,
104
+ body
105
+ )
106
+ }
107
+
108
+ // MARK: - Ranges as expected counts
109
+
110
+ /// Confirm that some event occurs during the invocation of a function.
111
+ ///
112
+ /// - Parameters:
113
+ /// - comment: An optional comment to apply to any issues generated by this
114
+ /// function.
115
+ /// - expectedCount: A range of integers indicating the number of times the
116
+ /// expected event should occur when `body` is invoked.
117
+ /// - sourceLocation: The source location to which any recorded issues should
118
+ /// be attributed.
119
+ /// - body: The function to invoke.
120
+ ///
121
+ /// - Returns: Whatever is returned by `body`.
122
+ ///
123
+ /// - Throws: Whatever is thrown by `body`.
124
+ ///
125
+ /// Use confirmations to check that an event occurs while a test is running in
126
+ /// complex scenarios where `#expect()` and `#require()` are insufficient. For
127
+ /// example, a confirmation may be useful when an expected event occurs:
128
+ ///
129
+ /// - In a context that cannot be awaited by the calling function such as an
130
+ /// event handler or delegate callback;
131
+ /// - More than once, or never; or
132
+ /// - As a callback that is invoked as part of a larger operation.
133
+ ///
134
+ /// To use a confirmation, pass a closure containing the work to be performed.
135
+ /// The testing library will then pass an instance of ``Confirmation`` to the
136
+ /// closure. Every time the event in question occurs, the closure should call
137
+ /// the confirmation:
138
+ ///
139
+ /// ```swift
140
+ /// let minBuns = 5
141
+ /// let maxBuns = 10
142
+ /// await confirmation(
143
+ /// "Baked between \(minBuns) and \(maxBuns) buns",
144
+ /// expectedCount: minBuns ... maxBuns
145
+ /// ) { bunBaked in
146
+ /// foodTruck.eventHandler = { event in
147
+ /// if event == .baked(.cinnamonBun) {
148
+ /// bunBaked()
149
+ /// }
150
+ /// }
151
+ /// await foodTruck.bakeTray(of: .cinnamonBun)
152
+ /// }
153
+ /// ```
154
+ ///
155
+ /// When the closure returns, the testing library checks if the confirmation's
156
+ /// preconditions have been met, and records an issue if they have not.
157
+ ///
158
+ /// If an exact count is expected, use
159
+ /// ``confirmation(_:expectedCount:sourceLocation:_:)-7kfko`` instead.
160
+ @_spi ( Experimental)
161
+ public func confirmation< R> (
162
+ _ comment: Comment ? = nil ,
163
+ expectedCount: some Confirmation . ExpectedCount ,
164
+ sourceLocation: SourceLocation = #_sourceLocation,
165
+ _ body: ( Confirmation ) async throws -> R
99
166
) async rethrows -> R {
100
167
let confirmation = Confirmation ( )
101
168
defer {
102
169
let actualCount = confirmation. count. rawValue
103
- if actualCount != expectedCount {
170
+ if ! expectedCount. contains ( actualCount ) {
104
171
Issue . record (
105
- . confirmationMiscounted ( actual : actualCount, expected : expectedCount ) ,
172
+ expectedCount . issueKind ( forActualCount : actualCount) ,
106
173
comments: Array ( comment) ,
107
174
backtrace: . current( ) ,
108
175
sourceLocation: sourceLocation
@@ -111,3 +178,52 @@ public func confirmation<R>(
111
178
}
112
179
return try await body ( confirmation)
113
180
}
181
+
182
+ @_spi ( Experimental)
183
+ extension Confirmation {
184
+ /// A protocol that describes a range expression that can be used with
185
+ /// ``confirmation(_:expectedCount:sourceLocation:_:)-41gmd``.
186
+ ///
187
+ /// This protocol represents any expression that describes a range of
188
+ /// confirmation counts. For example, the expression `1 ..< 10` automatically
189
+ /// conforms to it.
190
+ ///
191
+ /// You do not generally need to add conformances to this type yourself. It is
192
+ /// used by the testing library to abstract away the different range types
193
+ /// provided by the Swift standard library.
194
+ public protocol ExpectedCount : Sendable , RangeExpression < Int > { }
195
+ }
196
+
197
+ extension Confirmation . ExpectedCount {
198
+ /// Get an instance of ``Issue/Kind-swift.enum`` corresponding to this value.
199
+ ///
200
+ /// - Parameters:
201
+ /// - actualCount: The actual count for the failed confirmation.
202
+ ///
203
+ /// - Returns: An instance of ``Issue/Kind-swift.enum`` that describes `self`.
204
+ fileprivate func issueKind( forActualCount actualCount: Int ) -> Issue . Kind {
205
+ switch self {
206
+ case let expectedCount as ClosedRange < Int > where expectedCount. lowerBound == expectedCount. upperBound:
207
+ return . confirmationMiscounted( actual: actualCount, expected: expectedCount. lowerBound)
208
+ case let expectedCount as Range < Int > where expectedCount. lowerBound == expectedCount. upperBound - 1 :
209
+ return . confirmationMiscounted( actual: actualCount, expected: expectedCount. lowerBound)
210
+ default :
211
+ return . confirmationOutOfRange( actual: actualCount, expected: self )
212
+ }
213
+ }
214
+ }
215
+
216
+ @_spi ( Experimental)
217
+ extension ClosedRange < Int > : Confirmation . ExpectedCount { }
218
+
219
+ @_spi ( Experimental)
220
+ extension PartialRangeFrom < Int > : Confirmation . ExpectedCount { }
221
+
222
+ @_spi ( Experimental)
223
+ extension PartialRangeThrough < Int > : Confirmation . ExpectedCount { }
224
+
225
+ @_spi ( Experimental)
226
+ extension PartialRangeUpTo < Int > : Confirmation . ExpectedCount { }
227
+
228
+ @_spi ( Experimental)
229
+ extension Range < Int > : Confirmation . ExpectedCount { }
0 commit comments