MobiusNimble/Source/NimbleNextMatchers.swift (119 lines of code) (raw):

// Copyright Spotify AB. // SPDX-License-Identifier: Apache-2.0 import Foundation import MobiusCore import MobiusTest import Nimble /// Convenience function for creating assertions. /// /// - Parameter predicates: matchers an array of `Predicate`, all of which must match /// - Returns: an `Assert` that applies all the matchers public func assertThatNext<Model, Event, Effect>( _ predicates: Nimble.Predicate<Next<Model, Effect>>... ) -> UpdateSpec<Model, Event, Effect>.Assert { return { (result: UpdateSpec.Result) in predicates.forEach({ predicate in expect(result.lastNext).to(predicate) }) } } let haveNonNilNext = "have a non-nil Next. Got <nil>" let unexpectedNilParameterPredicate = Nimble.PredicateResult(bool: false, message: .expectedTo(haveNonNilNext)) /// - Returns: a `Predicate` that matches `Next` instances with no model and no effects. public func haveNothing<Model, Effect>() -> Nimble.Predicate<Next<Model, Effect>> { return haveNoModel() && haveNoEffects() } /// - Returns: a `Predicate` that matches `Next` instances without a model. public func haveNoModel<Model, Effect>() -> Nimble.Predicate<Next<Model, Effect>> { return Nimble.Predicate<Next<Model, Effect>>.define(matcher: { actualExpression in guard let next = try actualExpression.evaluate() else { return unexpectedNilParameterPredicate } var actualDescription = "" if let model = next.model { actualDescription = String(describing: model) } return Nimble.PredicateResult( bool: next.model == nil, message: .expectedCustomValueTo("have no model", actual: "<\(actualDescription)>") ) }) } /// - Returns: a `Predicate` that matches `Next` instances with a model. public func haveModel<Model, Effect>() -> Nimble.Predicate<Next<Model, Effect>> { return Nimble.Predicate<Next<Model, Effect>>.define(matcher: { actualExpression in guard let next = try actualExpression.evaluate() else { return unexpectedNilParameterPredicate } return Nimble.PredicateResult(bool: next.model != nil, message: .expectedTo("not have a <nil> model")) }) } /// - Parameter expected: the expected model /// - Returns: a `Predicate` that matches `Next` instances with a model that is equal to the supplied one. public func haveModel<Model: Equatable, Effect>(_ expected: Model) -> Nimble.Predicate<Next<Model, Effect>> { return Nimble.Predicate<Next<Model, Effect>>.define(matcher: { actualExpression in guard let next = try actualExpression.evaluate() else { return unexpectedNilParameterPredicate } guard let nextModel = next.model else { return Nimble.PredicateResult(bool: false, message: .expectedTo("have a model")) } let expectedDescription = String(describing: expected) let actualDescription = String(describing: nextModel) return Nimble.PredicateResult( bool: nextModel == expected, message: .expectedCustomValueTo("be <\(expectedDescription)>", actual: "<\(actualDescription)>") ) }) } /// - Returns: a `Predicate` that matches `Next` instances with no effects. public func haveNoEffects<Model, Effect>() -> Nimble.Predicate<Next<Model, Effect>> { return Nimble.Predicate<Next<Model, Effect>>.define(matcher: { actualExpression in guard let next = try actualExpression.evaluate() else { return unexpectedNilParameterPredicate } return Nimble.PredicateResult(bool: next.effects.isEmpty, message: .expectedTo("have no effects")) }) } /// Constructs a matcher that matches if all the supplied effects are present in the supplied `Next`, in any order. /// The `Next` may have more effects than the ones included. /// /// - Parameter expected: the effects to match (possibly empty) /// - Returns: a `Predicate` that matches `Next` instances that include all the supplied effects public func haveEffects<Model, Effect: Equatable>(_ expected: [Effect]) -> Nimble.Predicate<Next<Model, Effect>> { return Nimble.Predicate<Next<Model, Effect>>.define(matcher: { actualExpression in guard let next = try actualExpression.evaluate() else { return unexpectedNilParameterPredicate } let expectedDescription = String(describing: expected) let actualDescription = String(describing: next.effects) return Nimble.PredicateResult( bool: expected.allSatisfy(next.effects.contains), message: .expectedCustomValueTo( "contain <\(expectedDescription)>", actual: "<\(actualDescription)> (order doesn't matter)" ) ) }) } /// Constructs a matcher that matches if only the supplied effects are present in the supplied `Next`, in any order. /// /// - Parameter expected: the effects to match (possibly empty) /// - Returns: a `Predicate` that matches `Next` instances that include all the supplied effects public func haveOnlyEffects<Model, Effect: Equatable>(_ expected: [Effect]) -> Nimble.Predicate<Next<Model, Effect>> { return Nimble.Predicate<Next<Model, Effect>>.define(matcher: { actualExpression in guard let next = try actualExpression.evaluate() else { return unexpectedNilParameterPredicate } var unmatchedActual = next.effects var unmatchedExpected = expected zip(next.effects, expected).forEach { _ = unmatchedActual.firstIndex(of: $1).map { unmatchedActual.remove(at: $0) } _ = unmatchedExpected.firstIndex(of: $0).map { unmatchedExpected.remove(at: $0) } } let expectedDescription = String(describing: expected) let actualDescription = String(describing: next.effects) return Nimble.PredicateResult( bool: unmatchedActual.isEmpty && unmatchedExpected.isEmpty, message: .expectedCustomValueTo( "contain only <\(expectedDescription)>", actual: "<\(actualDescription)> (order doesn't matter)" ) ) }) } /// Constructs a matcher that matches if the supplied effects are equal to the supplied `Next`. /// /// - Parameter expected: the effects to match (possibly empty) /// - Returns: a `Predicate` that matches `Next` instances that include all the supplied effects public func haveExactlyEffects<Model, Effect: Equatable>(_ expected: [Effect]) -> Nimble.Predicate<Next<Model, Effect>> { return Nimble.Predicate<Next<Model, Effect>>.define(matcher: { actualExpression in guard let next = try actualExpression.evaluate() else { return unexpectedNilParameterPredicate } let expectedDescription = String(describing: expected) let actualDescription = String(describing: next.effects) return Nimble.PredicateResult( bool: expected == next.effects, message: .expectedCustomValueTo( "equal <\(expectedDescription)>", actual: "<\(actualDescription)>" ) ) }) }