Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions Sources/Nimble/Matchers/Map.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,37 @@ public func map<T, U>(_ transform: @escaping (T) async throws -> U, _ matcher: s
/// `map` works by transforming the expression to a value that the given matcher uses.
///
/// For example, you might only care that a particular property on a method equals some other value.
/// So, you could write `expect(myObject).to(map(\.someOptionalIntValue, equal(3))`.
/// This is also useful in conjunction with ``satisfyAllOf`` to do a partial equality of an object.
public func map<T, U>(_ transform: @escaping (T) throws -> U?, _ matcher: Matcher<U>) -> Matcher<T> {
Matcher { (received: Expression<T>) in
try matcher.satisfies(received.cast { value in
guard let value else { return nil }
return try transform(value)
})
}
}

/// `map` works by transforming the expression to a value that the given matcher uses.
///
/// For example, you might only care that a particular property on a method equals some other value.
/// So, you could write `expect(myObject).to(map(\.someOptionalIntValue, equal(3))`.
/// This is also useful in conjunction with ``satisfyAllOf`` to do a partial equality of an object.
public func map<T, U>(_ transform: @escaping (T) async throws -> U?, _ matcher: some AsyncableMatcher<U>) -> AsyncMatcher<T> {
AsyncMatcher { (received: AsyncExpression<T>) in
try await matcher.satisfies(received.cast { value in
guard let value else { return nil }
return try await transform(value)
})
}
}

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

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

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

Expand Down
87 changes: 87 additions & 0 deletions Tests/NimbleTests/Matchers/MapTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,91 @@ final class MapTest: XCTestCase {
map(\.string, equal("world"))
))
}

// MARK: Compact map
func testCompactMap() {
expect("1").to(compactMap({ Int($0) }, equal(1)))
expect("1").toNot(compactMap({ Int($0) }, equal(2)))

let assertions = gatherExpectations(silently: true) {
expect("not a number").to(compactMap({ Int($0) }, equal(1)))
expect("not a number").toNot(compactMap({ Int($0) }, equal(1)))
}

expect(assertions).to(haveCount(2))
expect(assertions.first?.success).to(beFalse())
expect(assertions.last?.success).to(beFalse())
}

func testCompactMapAsync() async {
struct Value {
let int: Int?
let string: String?
}

await expect("1").to(compactMap({ Int($0) }, asyncEqual(1)))
await expect("1").toNot(compactMap({ Int($0) }, asyncEqual(2)))

let assertions = await gatherExpectations(silently: true) {
await expect("not a number").to(compactMap({ Int($0) }, asyncEqual(1)))
await expect("not a number").toNot(compactMap({ Int($0) }, asyncEqual(1)))
}

expect(assertions).to(haveCount(2))
expect(assertions.first?.success).to(beFalse())
expect(assertions.last?.success).to(beFalse())
}

func testCompactMapWithAsyncFunction() async {
func someOperation(_ value: Int) async -> String? {
"\(value)"
}
await expect(1).to(compactMap(someOperation, equal("1")))

func someFailingOperation(_ value: Int) async -> String? {
nil
}

let assertions = await gatherExpectations(silently: true) {
await expect(1).to(compactMap(someFailingOperation, equal("1")))
await expect(1).toNot(compactMap(someFailingOperation, equal("1")))
}

expect(assertions).to(haveCount(2))
expect(assertions.first?.success).to(beFalse())
expect(assertions.last?.success).to(beFalse())
}

func testCompactMapWithActor() {
actor Box {
let int: Int?
let string: String?

init(int: Int?, string: String?) {
self.int = int
self.string = string
}
}

let box = Box(int: 3, string: "world")

expect(box).to(satisfyAllOf(
compactMap(\.int, equal(3)),
compactMap(\.string, equal("world"))
))

let failingBox = Box(int: nil, string: nil)

let assertions = gatherExpectations(silently: true) {
expect(failingBox).to(satisfyAllOf(
compactMap(\.int, equal(3))
))
expect(failingBox).toNot(satisfyAllOf(
compactMap(\.int, equal(3))
))
}
expect(assertions).to(haveCount(2))
expect(assertions.first?.success).to(beFalse())
expect(assertions.last?.success).to(beFalse())
}
}
Loading