Skip to content

Commit 725e5a3

Browse files
authored
Rename the failable map overload to compactMap (#1180)
And also provide another overload of map that takes in an optional and behaves much more closely to what the original map matcher does
1 parent 74185db commit 725e5a3

File tree

2 files changed

+118
-3
lines changed

2 files changed

+118
-3
lines changed

Sources/Nimble/Matchers/Map.swift

+31-3
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,37 @@ public func map<T, U>(_ transform: @escaping (T) async throws -> U, _ matcher: s
2929
/// `map` works by transforming the expression to a value that the given matcher uses.
3030
///
3131
/// For example, you might only care that a particular property on a method equals some other value.
32+
/// So, you could write `expect(myObject).to(map(\.someOptionalIntValue, equal(3))`.
33+
/// This is also useful in conjunction with ``satisfyAllOf`` to do a partial equality of an object.
34+
public func map<T, U>(_ transform: @escaping (T) throws -> U?, _ matcher: Matcher<U>) -> Matcher<T> {
35+
Matcher { (received: Expression<T>) in
36+
try matcher.satisfies(received.cast { value in
37+
guard let value else { return nil }
38+
return try transform(value)
39+
})
40+
}
41+
}
42+
43+
/// `map` works by transforming the expression to a value that the given matcher uses.
44+
///
45+
/// For example, you might only care that a particular property on a method equals some other value.
46+
/// So, you could write `expect(myObject).to(map(\.someOptionalIntValue, equal(3))`.
47+
/// This is also useful in conjunction with ``satisfyAllOf`` to do a partial equality of an object.
48+
public func map<T, U>(_ transform: @escaping (T) async throws -> U?, _ matcher: some AsyncableMatcher<U>) -> AsyncMatcher<T> {
49+
AsyncMatcher { (received: AsyncExpression<T>) in
50+
try await matcher.satisfies(received.cast { value in
51+
guard let value else { return nil }
52+
return try await transform(value)
53+
})
54+
}
55+
}
56+
57+
/// `compactMap` works by transforming the expression to a value that the given matcher uses.
58+
///
59+
/// For example, you might only care that a particular property on a method equals some other value.
3260
/// So, you could write `expect(myObject).to(compactMap({ $0 as? Int }, equal(3))`.
3361
/// This is also useful in conjunction with ``satisfyAllOf`` to match against a converted type.
34-
public func map<T, U>(_ transform: @escaping (T) throws -> U?, _ matcher: Matcher<U>) -> Matcher<T> {
62+
public func compactMap<T, U>(_ transform: @escaping (T) throws -> U?, _ matcher: Matcher<U>) -> Matcher<T> {
3563
Matcher { (received: Expression<T>) in
3664
let message = ExpectationMessage.expectedTo("Map from \(T.self) to \(U.self)")
3765

@@ -47,12 +75,12 @@ public func map<T, U>(_ transform: @escaping (T) throws -> U?, _ matcher: Matche
4775
}
4876
}
4977

50-
/// `map` works by transforming the expression to a value that the given matcher uses.
78+
/// `compactMap` works by transforming the expression to a value that the given matcher uses.
5179
///
5280
/// For example, you might only care that a particular property on a method equals some other value.
5381
/// So, you could write `expect(myObject).to(compactMap({ $0 as? Int }, equal(3))`.
5482
/// This is also useful in conjunction with ``satisfyAllOf`` to match against a converted type.
55-
public func map<T, U>(_ transform: @escaping (T) async throws -> U?, _ matcher: some AsyncableMatcher<U>) -> AsyncMatcher<T> {
83+
public func compactMap<T, U>(_ transform: @escaping (T) async throws -> U?, _ matcher: some AsyncableMatcher<U>) -> AsyncMatcher<T> {
5684
AsyncMatcher { (received: AsyncExpression<T>) in
5785
let message = ExpectationMessage.expectedTo("Map from \(T.self) to \(U.self)")
5886

Tests/NimbleTests/Matchers/MapTest.swift

+87
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,91 @@ final class MapTest: XCTestCase {
158158
map(\.string, equal("world"))
159159
))
160160
}
161+
162+
// MARK: Compact map
163+
func testCompactMap() {
164+
expect("1").to(compactMap({ Int($0) }, equal(1)))
165+
expect("1").toNot(compactMap({ Int($0) }, equal(2)))
166+
167+
let assertions = gatherExpectations(silently: true) {
168+
expect("not a number").to(compactMap({ Int($0) }, equal(1)))
169+
expect("not a number").toNot(compactMap({ Int($0) }, equal(1)))
170+
}
171+
172+
expect(assertions).to(haveCount(2))
173+
expect(assertions.first?.success).to(beFalse())
174+
expect(assertions.last?.success).to(beFalse())
175+
}
176+
177+
func testCompactMapAsync() async {
178+
struct Value {
179+
let int: Int?
180+
let string: String?
181+
}
182+
183+
await expect("1").to(compactMap({ Int($0) }, asyncEqual(1)))
184+
await expect("1").toNot(compactMap({ Int($0) }, asyncEqual(2)))
185+
186+
let assertions = await gatherExpectations(silently: true) {
187+
await expect("not a number").to(compactMap({ Int($0) }, asyncEqual(1)))
188+
await expect("not a number").toNot(compactMap({ Int($0) }, asyncEqual(1)))
189+
}
190+
191+
expect(assertions).to(haveCount(2))
192+
expect(assertions.first?.success).to(beFalse())
193+
expect(assertions.last?.success).to(beFalse())
194+
}
195+
196+
func testCompactMapWithAsyncFunction() async {
197+
func someOperation(_ value: Int) async -> String? {
198+
"\(value)"
199+
}
200+
await expect(1).to(compactMap(someOperation, equal("1")))
201+
202+
func someFailingOperation(_ value: Int) async -> String? {
203+
nil
204+
}
205+
206+
let assertions = await gatherExpectations(silently: true) {
207+
await expect(1).to(compactMap(someFailingOperation, equal("1")))
208+
await expect(1).toNot(compactMap(someFailingOperation, equal("1")))
209+
}
210+
211+
expect(assertions).to(haveCount(2))
212+
expect(assertions.first?.success).to(beFalse())
213+
expect(assertions.last?.success).to(beFalse())
214+
}
215+
216+
func testCompactMapWithActor() {
217+
actor Box {
218+
let int: Int?
219+
let string: String?
220+
221+
init(int: Int?, string: String?) {
222+
self.int = int
223+
self.string = string
224+
}
225+
}
226+
227+
let box = Box(int: 3, string: "world")
228+
229+
expect(box).to(satisfyAllOf(
230+
compactMap(\.int, equal(3)),
231+
compactMap(\.string, equal("world"))
232+
))
233+
234+
let failingBox = Box(int: nil, string: nil)
235+
236+
let assertions = gatherExpectations(silently: true) {
237+
expect(failingBox).to(satisfyAllOf(
238+
compactMap(\.int, equal(3))
239+
))
240+
expect(failingBox).toNot(satisfyAllOf(
241+
compactMap(\.int, equal(3))
242+
))
243+
}
244+
expect(assertions).to(haveCount(2))
245+
expect(assertions.first?.success).to(beFalse())
246+
expect(assertions.last?.success).to(beFalse())
247+
}
161248
}

0 commit comments

Comments
 (0)