Merge pull request #381 from robstoll/feature-new-infix-api

add feature to the new infix API
This commit is contained in:
Robert Stoll
2020-03-01 21:18:23 +01:00
committed by GitHub
30 changed files with 646 additions and 366 deletions

View File

@@ -42,7 +42,8 @@ fun <T, R> Expect<T>.feature(
* creates a new [Expect] for it and
* returns it so that subsequent calls are based on the feature.
*
* @return The newly created [Expect] for the return value of calling [f] on the current subject of the assertion.
* @return The newly created [Expect] for the return value of calling the method [f]
* on the current subject of the assertion.
*
* @since 0.9.0
*/
@@ -72,7 +73,8 @@ fun <T, R> Expect<T>.feature(
* creates a new [Expect] for it and
* returns it so that subsequent calls are based on the feature.
*
* @return The newly created [Expect] for the return value of calling [f] on the current subject of the assertion.
* @return The newly created [Expect] for the return value of calling the method [f]
* on the current subject of the assertion.
*
* @since 0.9.0
*/
@@ -106,7 +108,8 @@ fun <T, A1, R> Expect<T>.feature(
* creates a new [Expect] for it and
* returns it so that subsequent calls are based on the feature.
*
* @return The newly created [Expect] for the return value of calling [f] on the current subject of the assertion.
* @return The newly created [Expect] for the return value of calling the method [f]
* on the current subject of the assertion.
*
* @since 0.9.0
*/
@@ -140,7 +143,8 @@ fun <T, A1, A2, R> Expect<T>.feature(
* creates a new [Expect] for it and
* returns it so that subsequent calls are based on the feature.
*
* @return The newly created [Expect] for the return value of calling [f] on the current subject of the assertion.
* @return The newly created [Expect] for the return value of calling the method [f]
* on the current subject of the assertion.
*
* @since 0.9.0
*/
@@ -174,7 +178,8 @@ fun <T, A1, A2, A3, R> Expect<T>.feature(
* creates a new [Expect] for it and
* returns it so that subsequent calls are based on the feature.
*
* @return The newly created [Expect] for the return value of calling [f] on the current subject of the assertion.
* @return The newly created [Expect] for the return value of calling the method [f]
* on the current subject of the assertion.
*
* @since 0.9.0
*/

View File

@@ -4,21 +4,16 @@ import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.specs.feature0
import ch.tutteli.atrium.specs.feature1
import ch.tutteli.atrium.specs.notImplemented
import ch.tutteli.atrium.specs.withFeatureSuffix
class Fun0AssertionsSpec : ch.tutteli.atrium.specs.integration.Fun0AssertionsSpec(
"toThrow" to Companion::toThrowFeature,
"toThrow" to Companion::toThrow,
("toThrow" to ::toThrowFeature).withFeatureSuffix(),
"toThrow" to ::toThrow,
feature0<() -> Int, Int>(Expect<() -> Int>::notToThrow),
feature1<() -> Int, Expect<Int>.() -> Unit, Int>(Expect<() -> Int>::notToThrow),
"", "» "
) {
companion object {
fun toThrowFeature(expect: Expect<out () -> Any?>) = expect.toThrow<IllegalArgumentException>()
fun toThrow(expect: Expect<out () -> Any?>, assertionCreator: Expect<IllegalArgumentException>.() -> Unit) =
expect.toThrow<IllegalArgumentException> { assertionCreator() }
}
@Suppress("unused", "UNUSED_VALUE", "UNUSED_VARIABLE")
private fun ambiguityTest() {
val a1: Expect<() -> Any?> = notImplemented()
@@ -37,3 +32,9 @@ class Fun0AssertionsSpec : ch.tutteli.atrium.specs.integration.Fun0AssertionsSpe
val r8: Expect<Int> = a2.notToThrow {}
}
}
private fun toThrowFeature(expect: Expect<out () -> Any?>) =
expect.toThrow<IllegalArgumentException>()
private fun toThrow(expect: Expect<out () -> Any?>, assertionCreator: Expect<IllegalArgumentException>.() -> Unit) =
expect.toThrow<IllegalArgumentException> { assertionCreator() }

View File

@@ -3,10 +3,11 @@ package ch.tutteli.atrium.api.fluent.en_GB
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.specs.fun1
import ch.tutteli.atrium.specs.notImplemented
import ch.tutteli.atrium.specs.withNullableSuffix
object IterableAllAssertionsSpec : ch.tutteli.atrium.specs.integration.IterableAllAssertionsSpec(
fun1(Expect<Iterable<Double>>::all),
fun1(Expect<Iterable<Double?>>::all),
fun1(Expect<Iterable<Double?>>::all).withNullableSuffix(),
"◆ ", "❗❗ ", "", "» ", "▶ ", "◾ "
) {
@Suppress("unused", "UNUSED_VALUE")

View File

@@ -2,46 +2,26 @@ package ch.tutteli.atrium.api.fluent.en_GB
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.domain.builders.utils.mapArguments
import ch.tutteli.atrium.specs.fun0
import ch.tutteli.atrium.specs.fun1
import ch.tutteli.atrium.specs.fun2
import ch.tutteli.atrium.specs.notImplemented
import ch.tutteli.atrium.specs.*
import kotlin.jvm.JvmName
import kotlin.reflect.KFunction3
private fun <K, V, T> mfun2(
f: KFunction3<Expect<Map<out K, V>>, Pair<K, T>, Array<out Pair<K, T>>, Expect<Map<out K, V>>>
) = fun2(f)
class MapAssertionsSpec : ch.tutteli.atrium.specs.integration.MapAssertionsSpec(
fun2<Map<out String, Int>, Pair<String, Int>, Array<out Pair<String, Int>>>(Expect<Map<out String, Int>>::contains),
fun2<Map<out String?, Int?>, Pair<String?, Int?>, Array<out Pair<String?, Int?>>>(Expect<Map<out String?, Int?>>::contains),
"${containsKeyWithValueAssertionsFun.name} ${KeyValue::class.simpleName}" to Companion::containsKeyValue,
"${containsKeyWithNullableValueAssertionsFun.name} ${KeyValue::class.simpleName}" to Companion::containsNullable,
mfun2<String, Int, Int>(Expect<Map<out String, Int>>::contains),
mfun2<String?, Int?, Int?>(Expect<Map<out String?, Int?>>::contains).withNullableSuffix(),
mfun2<String, Int, Expect<Int>.() -> Unit>(::contains).adjustName { "$it ${KeyValue::class.simpleName}" },
mfun2<String?, Int?, (Expect<Int>.() -> Unit)?>(::contains).adjustName { "$it ${KeyValue::class.simpleName}" }.withNullableSuffix(),
fun1(Expect<Map<out String, *>>::containsKey),
fun1(Expect<Map<out String?, *>>::containsKey),
fun1(Expect<Map<out String?, *>>::containsKey).withNullableSuffix(),
fun1(Expect<Map<out String, *>>::containsNotKey),
fun1(Expect<Map<out String?, *>>::containsNotKey),
fun1(Expect<Map<out String?, *>>::containsNotKey).withNullableSuffix(),
fun0(Expect<Map<*, *>>::isEmpty),
fun0(Expect<Map<*, *>>::isNotEmpty)
) {
companion object {
//@formatter:off
private val containsKeyWithValueAssertionsFun : KFunction3<Expect<Map<out String, Int>>, KeyValue<String, Int>, Array<out KeyValue<String, Int>>, Expect<Map<out String, Int>>> = Expect<Map<out String, Int>>::contains
private val containsKeyWithNullableValueAssertionsFun : KFunction3<Expect<Map<out String?, Int?>>, KeyValue<String?, Int>, Array<out KeyValue<String?, Int>>, Expect<Map<out String?, Int?>>> = Expect<Map<out String?, Int?>>::contains
//@formatter:on
fun containsKeyValue(
expect: Expect<Map<out String, Int>>,
keyValue: Pair<String, Expect<Int>.() -> Unit>,
otherKeyValues: Array<out Pair<String, Expect<Int>.() -> Unit>>
) = mapArguments(keyValue, otherKeyValues).to { KeyValue(it.first, it.second) }.let { (first, others) ->
expect.contains(first, *others)
}
fun containsNullable(
expect: Expect<Map<out String?, Int?>>,
keyValue: Pair<String?, (Expect<Int>.() -> Unit)?>,
otherKeyValues: Array<out Pair<String?, (Expect<Int>.() -> Unit)?>>
) = mapArguments(keyValue, otherKeyValues).to { KeyValue(it.first, it.second) }.let { (first, others) ->
expect.contains(first, *others)
}
}
@Suppress("unused", "UNUSED_VALUE")
private fun ambiguityTest() {
@@ -208,3 +188,20 @@ class MapAssertionsSpec : ch.tutteli.atrium.specs.integration.MapAssertionsSpec(
}
}
private fun contains(
expect: Expect<Map<out String, Int>>,
keyValue: Pair<String, Expect<Int>.() -> Unit>,
otherKeyValues: Array<out Pair<String, Expect<Int>.() -> Unit>>
) = mapArguments(keyValue, otherKeyValues).to { KeyValue(it.first, it.second) }.let { (first, others) ->
expect.contains(first, *others)
}
@JvmName("containsNullable")
private fun contains(
expect: Expect<Map<out String?, Int?>>,
keyValue: Pair<String?, (Expect<Int>.() -> Unit)?>,
otherKeyValues: Array<out Pair<String?, (Expect<Int>.() -> Unit)?>>
) = mapArguments(keyValue, otherKeyValues).to { KeyValue(it.first, it.second) }.let { (first, others) ->
expect.contains(first, *others)
}

View File

@@ -171,10 +171,11 @@ inline infix fun <T> Expect<T>.and(@Suppress("UNUSED_PARAMETER") o: o): Expect<T
*
* @return An [Expect] for the current subject of the assertion.
*/
infix fun <T> Expect<T>.and(assertionCreator: Expect<T>.() -> Unit) = addAssertionsCreatedBy(assertionCreator)
infix fun <T> Expect<T>.and(assertionCreator: Expect<T>.() -> Unit): Expect<T> =
addAssertionsCreatedBy(assertionCreator)
/**
* Inline property referring actually to `this` and allows to write nicer sub-assertions.
* Inline property referring actually to `this` and allows to write infix assertions within an assertion group block
*
* For instance, instead of:
* ```

View File

@@ -0,0 +1,18 @@
package ch.tutteli.atrium.api.infix.en_GB.creating.feature
import kotlin.reflect.KProperty1
/**
* Parameter object which contains a [description] of a feature along with an [extractor]
* which actually extracts the feature out of a subject of an assertion.
*
* Use `of(K..., ...) { ... }` to create this representation where the first argument is the extractor in form of a
* [KProperty1] or a `KFunctionX` and the remaining arguments are the required arguments in case of a `KFunctionX`
* where `X` > 1.
*
* @property description The description of the feature.
* @property extractor The extractor which extracts the feature out of the subject of the assertion.
* @since 0.10.0
*/
data class Feature<T, R>(val description: String, val extractor: (T) -> R)

View File

@@ -0,0 +1,25 @@
package ch.tutteli.atrium.api.infix.en_GB.creating.feature
import ch.tutteli.atrium.creating.Expect
import kotlin.reflect.KProperty1
/**
* Parameter object which contains a [description] of a feature along with an [extractor]
* which actually extracts the feature out of a subject of an assertion + an [assertionCreator]
* which defines assertions for the feature.
*
* Use `of(K..., ...) { ... }` to create this representation where the first argument is the extractor in form of a
* [KProperty1] or a `KFunctionX`, the last an [assertionCreator]-lambda and the remaining arguments in-between the
* required arguments in case of a `KFunctionX` where `X` > 1.
*
* @property description The description of the feature.
* @property extractor The extractor which extracts the feature out of the subject of the assertion.
* @property assertionCreator The `assertionCreator`-lambda which defines assertions for the feature.
*
* @since 0.10.0
*/
data class FeatureWithCreator<T, R>(
val description: String,
val extractor: (T) -> R,
val assertionCreator: Expect<R>.() -> Unit
)

View File

@@ -0,0 +1,22 @@
package ch.tutteli.atrium.api.infix.en_GB.creating.feature
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.domain.builders.creating.MetaFeatureOption
import ch.tutteli.atrium.domain.creating.MetaFeature
/**
* Parameter object which combines a lambda with a [MetaFeatureOption] receiver (called [provider])
* and an [assertionCreator].
*
* Use the function `of({ ... }) { ... }` to create this representation where the first
* argument is a lambda with a [MetaFeatureOption] as receiver which has to create a [MetaFeature]
* where the subject of the assertion is available via implicit parameter `it`.
* Usually you use [f][MetaFeatureOption.f] to create a [MetaFeature],
* e.g. `feature of({ f(it::size) }) { o toBe 3 }`
*
* @since 0.10.0
*/
data class MetaFeatureOptionWithCreator<T, R>(
val provider: MetaFeatureOption<T>.(T) -> MetaFeature<R>,
val assertionCreator: Expect<R>.() -> Unit
)

View File

@@ -0,0 +1,14 @@
package ch.tutteli.atrium.api.infix.en_GB.creating.list
import ch.tutteli.atrium.creating.Expect
/**
* Parameter object which combines an [index] of type [Int] with an [assertionCreator] which defines assertions for
* a resulting feature of type [E].
*
* Use the function `index(Int) { ... }` to create this representation.
*
* @since 0.10.0
*/
data class IndexWithCreator<E>(val index: Int, val assertionCreator: Expect<E>.() -> Unit)

View File

@@ -1,40 +0,0 @@
package ch.tutteli.atrium.api.infix.en_GB.creating.list.get.builders
import ch.tutteli.atrium.api.infix.en_GB.creating.list.get.builders.impl.ListGetStepImpl
import ch.tutteli.atrium.creating.Expect
/**
* Represents the extension point for another step after a `get index`-step within a
* sophisticated `get` assertion building process for [List].
*
* @param E The element type of the [List].
* @param T A subtype of [List].
*/
interface ListGetStep<E, T : List<E>> {
/**
* The [Expect] for which this assertion is created
*/
val expect: Expect<T>
/**
* The given index which will be used to perform the [List.get].
*/
val index: Int
/**
* Makes the assertion that the given [index] is within the bounds of [Expect.subject] and that
* the corresponding entry holds all assertions the given [assertionCreator] might create for it.
*
* @return An [Expect] for the current subject of the assertion.
* @throws AssertionError Might throw an [AssertionError] if a created [Expect]s (by calling [assertionCreator])
* does not hold.
* @throws IllegalArgumentException in case the given [assertionCreator] did not create a single assertion.
*/
infix fun assertIt(assertionCreator: Expect<E>.() -> Unit): Expect<T>
companion object {
fun <E, T : List<E>> create(expect: Expect<T>, index: Int): ListGetStep<E, T> =
ListGetStepImpl(expect, index)
}
}

View File

@@ -1,14 +0,0 @@
package ch.tutteli.atrium.api.infix.en_GB.creating.list.get.builders.impl
import ch.tutteli.atrium.api.infix.en_GB.creating.list.get.builders.ListGetStep
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.domain.builders.ExpectImpl
internal class ListGetStepImpl<E, T: List<E>>(
override val expect: Expect<T>,
override val index: Int
) : ListGetStep<E, T> {
override infix fun assertIt(assertionCreator: Expect<E>.() -> Unit): Expect<T>
= expect.addAssertion(ExpectImpl.list.get(expect, index).collect(assertionCreator))
}

View File

@@ -0,0 +1,14 @@
package ch.tutteli.atrium.api.infix.en_GB.creating.map
import ch.tutteli.atrium.creating.Expect
/**
* Parameter object which combines an [key] of type [K] with an [assertionCreator] which defines assertions for
* a resulting feature of type [V].
*
* Use the function `key(...) { ... }` to create this representation where the first parameter corresponds
* to the [key] and the second is the [assertionCreator]
*
* @since 0.10.0
*/
data class KeyWithCreator<out K, V>(val key: K, val assertionCreator: Expect<V>.() -> Unit)

View File

@@ -1,47 +0,0 @@
package ch.tutteli.atrium.api.infix.en_GB.creating.map.get.builders
import ch.tutteli.atrium.api.infix.en_GB.creating.map.get.builders.impl.MapGetOptionImpl
import ch.tutteli.atrium.assertions.Assertion
import ch.tutteli.atrium.creating.AssertionPlant
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.creating.SubjectProvider
/**
* Represents the extension point for another option after a `get key`-step within a
* sophisticated `get` assertion building process for [Map].
*
* @param K The key type of the [Map].
* @param V the value type of the [Map].
* @param T A subtype of [Map].
*/
interface MapGetOption<K, V, T : Map<out K, V>> {
/**
* The [AssertionPlant] for which this assertion is created
*/
val expect: Expect<T>
/**
* The given key which will be used to perform the [Map.get].
*/
val key: K
/**
* Makes the assertion that the [Assert.subject][SubjectProvider.subject] contains the previously specified [key] and that the
* corresponding value holds all assertions the given [assertionCreator] might create for it.
*
* @return This expect to support a fluent API.
* @throws AssertionError Might throw an [AssertionError] if a created [Assertion]s (by calling [assertionCreator])
* does not hold.
* @throws IllegalArgumentException in case the given [assertionCreator] did not create a single assertion.
*/
infix fun assertIt(assertionCreator: Expect<V>.() -> Unit): Expect<T>
companion object {
/**
* Creates a [MapGetOption] based on the given [expect] and [key].
*/
fun <K, V, T : Map<out K, V>> create(expect: Expect<T>, key: K): MapGetOption<K, V, T> =
MapGetOptionImpl(expect, key)
}
}

View File

@@ -1,14 +0,0 @@
package ch.tutteli.atrium.api.infix.en_GB.creating.map.get.builders.impl
import ch.tutteli.atrium.api.infix.en_GB.creating.map.get.builders.MapGetOption
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.domain.builders.ExpectImpl
internal class MapGetOptionImpl<K, V, T : Map<out K, V>>(
override val expect: Expect<T>,
override val key: K
) : MapGetOption<K, V, T> {
override infix fun assertIt(assertionCreator: Expect<V>.() -> Unit): Expect<T> =
expect.addAssertion(ExpectImpl.map.getExisting(expect, key).collect(assertionCreator))
}

View File

@@ -0,0 +1,231 @@
package ch.tutteli.atrium.api.infix.en_GB
import ch.tutteli.atrium.api.infix.en_GB.creating.feature.Feature
import ch.tutteli.atrium.api.infix.en_GB.creating.feature.FeatureWithCreator
import ch.tutteli.atrium.api.infix.en_GB.creating.feature.MetaFeatureOptionWithCreator
import ch.tutteli.atrium.assertions.AssertionGroup
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.creating.FeatureExpect
import ch.tutteli.atrium.domain.builders.ExpectImpl
import ch.tutteli.atrium.domain.builders.creating.MetaFeatureOption
import ch.tutteli.atrium.domain.creating.MetaFeature
import kotlin.reflect.*
/**
* Extracts the [property] out of the current subject of the assertion,
* creates a new [Expect] for it and
* returns it so that subsequent calls are based on the feature.
*
* @return The newly created [Expect] for the given [property].
*
* @since 0.10.0
*/
infix fun <T, R> Expect<T>.feature(property: KProperty1<T, R>): FeatureExpect<T, R> =
ExpectImpl.feature.property(this, property).getExpectOfFeature()
/**
* Extracts the value which is returned when calling the method [f] on the current subject of the assertion,
* creates a new [Expect] for it and
* returns it so that subsequent calls are based on the feature.
*
* Use `feature of(...)` in case the method requires parameters or in case you want to define
* an assertion group block for it.
*
* @return The newly created [Expect] for the return value of calling the method [f]
* on the current subject of the assertion.
*
* @since 0.10.0
*/
infix fun <T, R> Expect<T>.feature(f: KFunction1<T, R>): FeatureExpect<T, R> =
ExpectImpl.feature.f0(this, f).getExpectOfFeature()
/**
* Extracts a feature out of the current subject of the assertion using the given [Feature.extractor],
* creates a new [Expect] for it and
* returns it so that subsequent calls are based on the feature.
*
* Use `of(K..., ...)` to create a [Feature] where the first argument is the extractor in form of a
* [KProperty1] or a `KFunctionX` and potentially the required arguments for a `KFunctionX` where `X` > 1.
*
* @param of Use `of(K..., ...)` to create a [Feature] where the first argument is the extractor in form of a
* [KProperty1] or a `KFunctionX` and potentially the required arguments for a `KFunctionX` where `X` > 1.
*
* @return The newly created [Expect] for the extracted feature.
*
* @since 0.10.0
*/
infix fun <T, R> Expect<T>.feature(of: Feature<T, R>): FeatureExpect<T, R> =
ExpectImpl.feature.manualFeature(this, of.description, of.extractor).getExpectOfFeature()
/**
* Extracts a feature out of the current subject of the assertion using the given [FeatureWithCreator.extractor],
* creates a new [Expect] for it,
* applies an assertion group based on the given [FeatureWithCreator.assertionCreator] for the feature and
* returns the initial [Expect] with the current subject.
*
* Use `of(K..., ...) { ... }` to create a [FeatureWithCreator] where the first argument is the extractor in
* form of a [KProperty1] or a `KFunctionX`, the last an `assertionCreator`-lambda and the remaining arguments
* in-between the required arguments in case of a `KFunctionX` where `X` > 1.
*
* @param of Use `of(K..., ...) { ... }` to create a [FeatureWithCreator] where the first argument is the extractor in
* form of a [KProperty1] or a `KFunctionX`, the last an `assertionCreator`-lambda and the remaining arguments
* in-between the required arguments in case of a `KFunctionX` where `X` > 1.
*
* @return An [Expect] for the current subject of the assertion.
* @throws AssertionError Might throw an [AssertionError] in case the created [AssertionGroup] does not hold.
*
* @since 0.10.0
*/
infix fun <T, R> Expect<T>.feature(of: FeatureWithCreator<T, R>): Expect<T> =
ExpectImpl.feature.manualFeature(this, of.description, of.extractor).addToInitial(of.assertionCreator)
/**
* Extracts a feature out of the current subject of the assertion,
* based on the given [provider],
* creates a new [Expect] for it and
* returns it so that subsequent calls are based on the feature.
*
* @param provider Creates a [MetaFeature] where the subject of the assertion is available via
* implicit parameter `it`. Usually you use [f][MetaFeatureOption.f] to create a [MetaFeature],
* e.g. `feature { f(it::size) }`
*
* @return The newly created [Expect] for the extracted feature.
*
* @since 0.10.0
*/
infix fun <T, R> Expect<T>.feature(provider: MetaFeatureOption<T>.(T) -> MetaFeature<R>): FeatureExpect<T, R> =
ExpectImpl.feature.genericSubjectBasedFeature(this) { MetaFeatureOption(this).provider(it) }.getExpectOfFeature()
/**
* Extracts a feature out of the current subject of the assertion,
* based on the given [MetaFeatureOptionWithCreator]
* creates a new [Expect] for it,
* applies an assertion group based on the given [MetaFeatureOptionWithCreator.assertionCreator] for the feature and
* returns the initial [Expect] with the current subject.
*
* Note that you need to enable the new type inference of Kotlin (or use Kotlin 1.4 and above) in order that Kotlin
* is able to infer the types.
* As workaround you can use the overload which expects `MetaFeatureOption<T>.(T) -> MetaFeature<R>`
* and use `it` after the call (import from the package workaround). For instance:
*
* ```
* // use
* expect(person) feature { f(it::age) } it { o toBe 20 }
*
* // instead of (which causes problems with Kotlin < 1.4)
* expect(person) feature of({ f(it::age) }) { o toBe 20 }
* ```
*
* @param of Use the function `of({ ... }) { ... }` to create the [MetaFeatureOptionWithCreator] where the first
* argument is a lambda with a [MetaFeatureOption] as receiver which has to create a [MetaFeature]
* where the subject of the assertion is available via implicit parameter `it`.
* Usually you use [f][MetaFeatureOption.f] to create a [MetaFeature],
* e.g. `feature of({ f(it::size) }) { o toBe 3 }`
*
* @return An [Expect] for the current subject of the assertion.
* @since 0.10.0
*/
infix fun <T, R> Expect<T>.feature(of: MetaFeatureOptionWithCreator<T, R>): Expect<T> =
ExpectImpl.feature.genericSubjectBasedFeature(this) {
MetaFeatureOption(this).(of.provider)(it)
}.addToInitial(of.assertionCreator)
/**
* Creates a [MetaFeature] using the given [provider] and [description].
*
* This can be used to create complex features with a custom description or as workaround where Kotlin is not able to
* infer the types properly.
*
* For instance:
* ```
* expect(person) feature { f("first underage child", it.children.first { it < 18 }) }
* ```
*
* @return The newly created [MetaFeature].
*/
@Suppress("unused" /* unused receiver, but that's fine */)
fun <T, R> MetaFeatureOption<T>.f(description: String, provider: R): MetaFeature<R> =
MetaFeature(description, provider)
//@formatter:off
/**
* Helper function to create a [Feature] based on a [KFunction2] + arguments.
*/
fun <T, A1, R> of(f: KFunction2<T, A1, R>, a1: A1): Feature<T, R> =
Feature(f.name) { f.invoke(it, a1) }
/**
* Helper function to create a [Feature] based on a [KFunction3] + arguments.
*/
fun <T, A1, A2, R > of(f: KFunction3<T, A1, A2, R>, a1: A1, a2: A2): Feature<T, R> =
Feature(f.name) { f.invoke(it, a1, a2) }
/**
* Helper function to create a [Feature] based on a [KFunction4] + arguments.
*/
fun <T, A1, A2, A3, R> of(f: KFunction4<T, A1, A2, A3, R>, a1: A1, a2: A2, a3: A3): Feature<T, R> =
Feature(f.name) { f.invoke(it, a1, a2, a3) }
/**
* Helper function to create a [Feature] based on a [KFunction5] + arguments.
*/
fun <T, A1, A2, A3, A4, R> of(f: KFunction5<T, A1, A2, A3, A4, R>, a1: A1, a2: A2, a3: A3, a4: A4): Feature<T, R> =
Feature(f.name) { f.invoke(it, a1, a2, a3, a4) }
/**
* Helper function to create a [Feature] based on a [KFunction5] + arguments.
*/
fun <T, A1, A2, A3, A4, A5, R> of(f: KFunction6<T, A1, A2, A3, A4, A5, R>, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5): Feature<T, R> =
Feature(f.name) { f.invoke(it, a1, a2, a3, a4, a5) }
/**
* Helper function to create a [FeatureWithCreator] based on a [KProperty1] + [assertionCreator].
*/
fun <T, R> of(property: KProperty1<T, R>, assertionCreator: Expect<R>.() -> Unit): FeatureWithCreator<T, R> =
FeatureWithCreator(property.name, { property.invoke(it) }, assertionCreator)
/**
* Helper function to create a [FeatureWithCreator] based on a [KFunction1] + [assertionCreator].
*/
fun <T, R> of(f: KFunction1<T, R>, assertionCreator: Expect<R>.() -> Unit): FeatureWithCreator<T, R> =
FeatureWithCreator(f.name, { f.invoke(it) }, assertionCreator)
/**
* Helper function to create a [FeatureWithCreator] based on a [KFunction2] + arguments + [assertionCreator].
*/
fun <T, A1, R> of(f: KFunction2<T, A1, R>, a1: A1, assertionCreator: Expect<R>.() -> Unit): FeatureWithCreator<T, R> =
FeatureWithCreator(f.name, { f.invoke(it, a1) }, assertionCreator)
/**
* Helper function to create a [FeatureWithCreator] based on a [KFunction3] + arguments + [assertionCreator].
*/
fun <T, A1, A2, R > of(f: KFunction3<T, A1, A2, R>, a1: A1, a2: A2, assertionCreator: Expect<R>.() -> Unit): FeatureWithCreator<T, R> =
FeatureWithCreator(f.name, { f.invoke(it, a1, a2) }, assertionCreator)
/**
* Helper function to create a [FeatureWithCreator] based on a [KFunction4] + arguments + [assertionCreator].
*/
fun <T, A1, A2, A3, R> of(f: KFunction4<T, A1, A2, A3, R>, a1: A1, a2: A2, a3: A3, assertionCreator: Expect<R>.() -> Unit): FeatureWithCreator<T, R> =
FeatureWithCreator(f.name, { f.invoke(it, a1, a2, a3) }, assertionCreator)
/**
* Helper function to create a [FeatureWithCreator] based on a [KFunction5] + arguments + [assertionCreator].
*/
fun <T, A1, A2, A3, A4, R> of(f: KFunction5<T, A1, A2, A3, A4, R>, a1: A1, a2: A2, a3: A3, a4: A4, assertionCreator: Expect<R>.() -> Unit): FeatureWithCreator<T, R> =
FeatureWithCreator(f.name, { f.invoke(it, a1, a2, a3, a4) }, assertionCreator)
//@formatter:on
/**
* Helper function to create a [MetaFeatureOptionWithCreator] based on a lambda with
* [MetaFeatureOption] receiver (has to return a [MetaFeature]) and an [assertionCreator].
*/
fun <T, R> of(
provider: MetaFeatureOption<T>.(T) -> MetaFeature<R>,
assertionCreator: Expect<R>.() -> Unit
): MetaFeatureOptionWithCreator<T, R> =
MetaFeatureOptionWithCreator(
provider,
assertionCreator
)

View File

@@ -27,40 +27,48 @@ object Blank : Keyword
/**
* Represents the pseudo keyword `contain` as in [to] `contain`.
* It can be used for a parameter less function so that it has one parameter and thus can be used as infix function.
*/
object contain : Keyword
/**
* Represents the pseudo keyword `case` as in [ignoring] `case`.
* It can be used for a parameter less function so that it has one parameter and thus can be used as infix function.
*/
object case : Keyword
/**
* Represents the pseudo keyword `entries` as in [grouped] `entries`.
* It can be used for a parameter less function so that it has one parameter and thus can be used as infix function.
*/
object entries : Keyword
/**
* Represents the pseudo keyword `group` as in [within] `group`.
* It can be used for a parameter less function so that it has one parameter and thus can be used as infix function.
*/
object group : Keyword
/**
* Represents a filler, a pseudo keyword where there isn't really a good keyword.
* A reader should skip this filler without reading it. For instance, `contains o atleast 1...` should be read as
* A reader should skip this filler without reading it. For instance, `contains o atLeast 1...` should be read as
* `contains at least once...`
*
* It can be used for a parameter less function so that it has one parameter and thus can be used as infix function.
*
* @since 0.10.0
*/
object o : Keyword
/**
* Represents the pseudo keyword `only` as in [and] `only`.
* It can be used for a parameter less function so that it has one parameter and thus can be used as infix function.
*/
object only : Keyword
/**
* Represents the pseudo keyword `order` as in [inAny] `order`.
* It can be used for a parameter less function so that it has one parameter and thus can be used as infix function.
*/
object order : Keyword

View File

@@ -1,6 +1,6 @@
package ch.tutteli.atrium.api.infix.en_GB
import ch.tutteli.atrium.api.infix.en_GB.creating.list.get.builders.ListGetStep
import ch.tutteli.atrium.api.infix.en_GB.creating.list.IndexWithCreator
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.domain.builders.ExpectImpl
@@ -9,15 +9,26 @@ import ch.tutteli.atrium.domain.builders.ExpectImpl
* returns an [Expect] for the element at that position.
*
* @return The newly created [Expect] for the element at position [index].
* @throws AssertionError if the given [index] is out of bound.
* @throws AssertionError Might throw an [AssertionError] if the given [index] is out of bound.
*/
infix fun <E, T : List<E>> Expect<T>.get(index: Int): Expect<E> =
ExpectImpl.list.get(this, index).getExpectOfFeature()
/**
* Prepares the assertion about the return value of calling [get][List.get] with the given [index].
* Expects that the given [index][IndexWithCreator.index] is within the bounds of the subject of the assertion
* (a [List]) and that the element at that position holds all assertions the given
* [IndexWithCreator.assertionCreator] creates for it.
*
* @return A fluent builder to finish the assertion.
* Use the function `index(Int) { ... }` to create an [IndexWithCreator].
*
* @return This assertion container to support a fluent API.
* @throws AssertionError Might throw an [AssertionError] if the given [index] is out of bound or
* if the assertion made is not correct.
*/
infix fun <E, T : List<E>> Expect<T>.get(index: Index): ListGetStep<E, T> =
ListGetStep.create(this, index.index)
infix fun <E, T : List<E>> Expect<T>.get(index: IndexWithCreator<E>): Expect<T> =
ExpectImpl.list.get(this, index.index).addToInitial(index.assertionCreator)
/**
* Helper function to create an [IndexWithCreator] based on the given [index] and [assertionCreator].
*/
fun <E> index(index: Int, assertionCreator: Expect<E>.() -> Unit) = IndexWithCreator(index, assertionCreator)

View File

@@ -1,6 +1,7 @@
package ch.tutteli.atrium.api.infix.en_GB
import ch.tutteli.atrium.api.infix.en_GB.creating.map.get.builders.MapGetOption
import ch.tutteli.atrium.api.infix.en_GB.creating.map.KeyWithCreator
import ch.tutteli.atrium.assertions.Assertion
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.domain.builders.ExpectImpl
@@ -87,12 +88,23 @@ infix fun <K, V, T : Map<out K, V>> Expect<T>.getExisting(key: K): Expect<V> =
ExpectImpl.map.getExisting(this, key).getExpectOfFeature()
/**
* Prepares the assertion about the return value of calling [get][Map.get] with the given [key].
* Expects that the subject of the assertion (a [Map]) contains the given [key] and that
* the corresponding value holds all assertions the given [KeyWithCreator.assertionCreator] creates for it.
*
* @return A fluent builder to finish the assertion.
* */
infix fun <K, V, T : Map<out K, V>> Expect<T>.getExisting(key: Key<K>): MapGetOption<K, V, T> =
MapGetOption.create(this, key.key)
* @param key Use the function `key(...) { ... }` to create a [KeyWithCreator] where the first parameter corresponds
* to the key and the second is the `assertionCreator`-lambda
*
* @return An [Expect] for the current subject of the assertion.
* @throws AssertionError Might throw an [AssertionError] the given [key] does not exist or
* if the assertion made is not correct.
*/
infix fun <K, V, T : Map<out K, V>> Expect<T>.getExisting(key: KeyWithCreator<K, V>): Expect<T> =
ExpectImpl.map.getExisting(this, key.key).addToInitial(key.assertionCreator)
/**
* Helper function to create an [KeyWithCreator] based on the given [key] and [assertionCreator].
*/
fun <K, V> key(key: K, assertionCreator: Expect<V>.() -> Unit) = KeyWithCreator(key, assertionCreator)
/**
@@ -178,3 +190,4 @@ fun <K, V, T : Map<out K, V>> Expect<T>.asEntries(): Expect<Set<Map.Entry<K, V>>
infix fun <K, V, T : Map<out K, V>> Expect<T>.asEntries(
assertionCreator: Expect<Set<Map.Entry<K, V>>>.() -> Unit
): Expect<T> = apply { asEntries().addAssertionsCreatedBy(assertionCreator) }

View File

@@ -4,31 +4,24 @@ import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.domain.builders.utils.VarArgHelper
/**
* Wrapper for a single index -- can be used as distinguishable type for an overload where Int is already in use.
*/
data class Index(val index: Int)
data class Key<out K>(val key: K)
/**
* Parameter object to express `T, vararg T` in the infix-api.
* Parameter object to express `T, vararg T`.
*/
class All<out T>(override val expected: T, override vararg val otherExpected: T) : VarArgHelper<T>
/**
* Parameter object to express `Pair<K, V>, vararg Pair<K, V>` in the infix-api.
*/
class Pairs<out K, out V>(
override val expected: Pair<K, V>,
override vararg val otherExpected: Pair<K, V>
) : VarArgHelper<Pair<K, V>>
/**
* Parameter object to express a key/value [Pair] whose value type is a lambda with an
* [Assert][AssertionPlant] receiver, which means one can either pass a lambda or `null`.
* Parameter object to express a key/value [Pair] whose value type is a nullable lambda with an
* [Expect] receiver, which means one can either pass a lambda or `null`.
*/
data class KeyValue<out K, V : Any>(val key: K, val valueAssertionCreatorOrNull: (Expect<V>.() -> Unit)?) {
fun toPair(): Pair<K, (Expect<V>.() -> Unit)?> = key to valueAssertionCreatorOrNull
override fun toString(): String =
"KeyValue(key=$key, value=${if (valueAssertionCreatorOrNull == null) "null" else "lambda"})"
}
/**
* Parameter object to express `Pair<K, V>, vararg Pair<K, V>`.
*/
class Pairs<out K, out V>(
override val expected: Pair<K, V>,
override vararg val otherExpected: Pair<K, V>
) : VarArgHelper<Pair<K, V>>

View File

@@ -1,3 +1,5 @@
package ch.tutteli.atrium.api.infix.en_GB
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.domain.builders.ExpectImpl.changeSubject
@@ -9,8 +11,8 @@ import ch.tutteli.atrium.domain.builders.ExpectImpl.changeSubject
*
* @return The newly created [Expect] for the transformed subject.
*/
fun <E, T : Sequence<E>> Expect<T>.asIterable(): Expect<Iterable<E>>
= changeSubject(this).unreported { it.asIterable() }
fun <E, T : Sequence<E>> Expect<T>.asIterable(): Expect<Iterable<E>> =
changeSubject(this).unreported { it.asIterable() }
/**
* Expects that the subject of the assertion holds all assertions the given [assertionCreator] creates for

View File

@@ -0,0 +1,25 @@
package ch.tutteli.atrium.api.infix.en_GB.workaround
import ch.tutteli.atrium.api.infix.en_GB.and
import ch.tutteli.atrium.creating.Expect
/**
* Can be used to create a group of sub assertions when using the fluent API.
*
* Intended to be used in combination with feature assertions where Kotlin < 1.4 is not able to infer the correct type.
* For instance:
* ```
* // use
* expect(person) feature { f(it::age) } it { o toBe 20 }
*
* // instead of (which causes problems with Kotlin < 1.4)
* expect(person) feature of({ f(it::age) }) { o toBe 20 }
* ```
*
* Note that this workaround will be removed in some minor version after a major version with Kotlin 1.4 support
* (most likely with Atrium v1.1.0 where Atrium v1.0.0 requires Kotlin 1.4)
*
* @return An [Expect] for the current subject of the assertion.
*/
@Suppress("NOTHING_TO_INLINE" /* inline so that one does not actually call `it` on binary level */)
inline infix fun <T> Expect<T>.it(noinline assertionCreator: Expect<T>.() -> Unit): Expect<T> = and(assertionCreator)

View File

@@ -81,7 +81,7 @@ class AnyAssertionsSpec : ch.tutteli.atrium.specs.integration.AnyAssertionsSpec(
//regression for #298, should compile without the need for E : Any or List<E?>
@Suppress("unused")
fun <E> Expect<List<E>>.firstIs(value: E) = o get Index(0) assertIt { o toBe value }
fun <E> Expect<List<E>>.firstIs(value: E) = o get index(0) { o toBe value }
}
private fun toBeNull(expect: Expect<Int?>) = expect toBe null

View File

@@ -6,7 +6,7 @@ import ch.tutteli.atrium.specs.testutils.WithAsciiReporter
class CollectionAssertionsSpec : ch.tutteli.atrium.specs.integration.CollectionAssertionsSpec(
"toBe ${Empty::class.simpleName}" to ::isEmpty,
"toBe ${Empty::class.simpleName}" to ::isNotEmpty
"notToBe ${Empty::class.simpleName}" to ::isNotEmpty
) {
companion object : WithAsciiReporter()
@@ -28,5 +28,5 @@ class CollectionAssertionsSpec : ch.tutteli.atrium.specs.integration.CollectionA
}
}
fun isEmpty(expect: Expect<Collection<Int>>) = expect toBe Empty
fun isNotEmpty(expect: Expect<Collection<Int>>) = expect notToBe Empty
private fun isEmpty(expect: Expect<Collection<Int>>) = expect toBe Empty
private fun isNotEmpty(expect: Expect<Collection<Int>>) = expect notToBe Empty

View File

@@ -1,15 +1,14 @@
package ch.tutteli.atrium.api.infix.en_GB
import ch.tutteli.atrium.specs.testutils.WithAsciiReporter
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.specs.fun1
import ch.tutteli.atrium.specs.name
import ch.tutteli.atrium.specs.notImplemented
import ch.tutteli.atrium.specs.testutils.WithAsciiReporter
import ch.tutteli.atrium.specs.withNullableSuffix
class IterableAllAssertionsSpec : ch.tutteli.atrium.specs.integration.IterableAllAssertionsSpec(
fun1(Expect<Iterable<Double>>::all).name to ::all,
fun1(Expect<Iterable<Double?>>::all).withNullableSuffix().name to ::allNullable,
fun1(Expect<Iterable<Double>>::all),
fun1(Expect<Iterable<Double?>>::all).withNullableSuffix(),
"* ", "(!) ", "- ", "» ", ">> ", "=> "
) {
companion object : WithAsciiReporter()
@@ -21,17 +20,11 @@ class IterableAllAssertionsSpec : ch.tutteli.atrium.specs.integration.IterableAl
var star: Expect<Iterable<*>> = notImplemented()
a1 = a1.all {}
a1 = a1 all {}
a1b = a1b.all {}
a1b = a1b.all(null)
a1b = a1b all {}
a1b = a1b all null
star = star.all {}
star = star all {}
}
}
private fun all(expect: Expect<Iterable<Double>>, assertionCreator: Expect<Double>.() -> Unit) =
expect all assertionCreator
private fun allNullable(expect: Expect<Iterable<Double?>>, assertionCreator: (Expect<Double>.() -> Unit)?) =
expect all assertionCreator

View File

@@ -1,18 +1,18 @@
package ch.tutteli.atrium.api.infix.en_GB
import ch.tutteli.atrium.api.infix.en_GB.creating.list.get.builders.ListGetStep
import ch.tutteli.atrium.specs.testutils.WithAsciiReporter
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.specs.feature1
import ch.tutteli.atrium.specs.fun2
import ch.tutteli.atrium.specs.notImplemented
import ch.tutteli.atrium.specs.testutils.WithAsciiReporter
import ch.tutteli.atrium.specs.withNullableSuffix
import kotlin.reflect.KFunction2
import kotlin.jvm.JvmName
class ListFeatureAssertionsSpec : ch.tutteli.atrium.specs.integration.ListFeatureAssertionsSpec(
feature1<List<Int>, Int, Int>(Expect<List<Int>>::get),
getIndexPair(),
fun2<List<Int>, Int, Expect<Int>.() -> Unit>(::get),
feature1<List<Int?>, Int, Int?>(Expect<List<Int?>>::get).withNullableSuffix(),
getIndexNullablePair()
fun2<List<Int?>, Int, Expect<Int?>.() -> Unit>(::get).withNullableSuffix()
) {
companion object : WithAsciiReporter()
@@ -24,29 +24,22 @@ class ListFeatureAssertionsSpec : ch.tutteli.atrium.specs.integration.ListFeatur
var star: Expect<out List<*>> = notImplemented()
a1 get 1
a1 = a1 get Index(1) assertIt { }
a1 = a1 get index(1) { }
a1b get 1
a1b = a1b get Index(1) assertIt { }
a1b = a1b get index(1) { }
star get 1
star = star get Index(1) assertIt { }
star = star get index(1) { }
}
}
private val getIndexFun: KFunction2<Expect<List<Int>>, Index, ListGetStep<Int, List<Int>>> = Expect<List<Int>>::get
private fun getIndexPair() = getIndexFun.name to ::getIndex
private fun get(expect: Expect<List<Int>>, index: Int, assertionCreator: Expect<Int>.() -> Unit) =
expect get index(index) { assertionCreator() }
private fun getIndex(expect: Expect<List<Int>>, index: Int, assertionCreator: Expect<Int>.() -> Unit) =
expect get Index(index) assertIt { assertionCreator() }
private val getIndexNullableFun: KFunction2<Expect<List<Int?>>, Index, ListGetStep<Int?, List<Int?>>> =
Expect<List<Int?>>::get
private fun getIndexNullablePair() = getIndexNullableFun.name to ::getIndexNullable
private fun getIndexNullable(
@JvmName("getNullable")
private fun get(
expect: Expect<List<Int?>>,
index: Int,
assertionCreator: Expect<Int?>.() -> Unit
) = expect get Index(index) assertIt { assertionCreator() }
) = expect get index(index) { assertionCreator() }

View File

@@ -3,86 +3,27 @@ package ch.tutteli.atrium.api.infix.en_GB
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.domain.builders.utils.mapArguments
import ch.tutteli.atrium.specs.*
import ch.tutteli.atrium.specs.testutils.WithAsciiReporter
import kotlin.jvm.JvmName
import kotlin.reflect.KFunction3
private fun <K, V, T> mfun2(
f: KFunction3<Expect<Map<out K, V>>, Pair<K, T>, Array<out Pair<K, T>>, Expect<Map<out K, V>>>
) = fun2(f)
class MapAssertionsSpec : ch.tutteli.atrium.specs.integration.MapAssertionsSpec(
fun2(Companion::contains),
fun2(Companion::contains).name to Companion::containsNullable,
"${fun2(Companion::contains).name} ${KeyValue::class.simpleName}" to Companion::containsKeyWithValueAssertions,
"${fun2(Companion::contains).name} ${KeyValue::class.simpleName}" to Companion::containsKeyWithNullableValueAssertions,
fun1(Companion::containsKey),
fun1(Companion::containsNullableKey),
fun1(Companion::containsNotKey),
fun1(Companion::containsNotNullableKey),
/* string toBe, notToBe to avoid ambiguity error */
"toBe ${Empty::class.simpleName}" to Companion::isEmpty,
"notToBe ${Empty::class.simpleName}" to Companion::isNotEmpty
mfun2<String, Int, Int>(::contains),
mfun2<String?, Int?, Int?>(::contains).withNullableSuffix(),
mfun2<String, Int, Expect<Int>.() -> Unit>(::contains).adjustName { "$it ${KeyValue::class.simpleName}" },
mfun2<String?, Int?, (Expect<Int>.() -> Unit)?>(::contains).adjustName { "$it ${KeyValue::class.simpleName}" }.withNullableSuffix(),
fun1<Map<out String, *>, String>(::containsKey),
fun1<Map<out String?, *>, String?>(::containsKey).withNullableSuffix(),
fun1<Map<out String, *>, String>(::containsNotKey),
fun1<Map<out String?, *>, String?>(::containsNotKey).withNullableSuffix(),
"toBe ${Empty::class.simpleName}" to ::isEmpty,
"notToBe ${Empty::class.simpleName}" to ::isNotEmpty
) {
companion object {
private fun contains(
expect: Expect<Map<out String, Int>>,
pair: Pair<String, Int>,
otherPairs: Array<out Pair<String, Int>>
): Expect<Map<out String, Int>> {
return if (otherPairs.isEmpty()) {
expect contains (pair.first to pair.second)
} else {
expect contains Pairs(pair, *otherPairs)
}
}
private fun containsNullable(
expect: Expect<Map<out String?, Int?>>,
pair: Pair<String?, Int?>,
otherPairs: Array<out Pair<String?, Int?>>
): Expect<Map<out String?, Int?>> {
return if (otherPairs.isEmpty()) {
expect contains (pair.first to pair.second)
} else {
expect contains Pairs(pair, *otherPairs)
}
}
private fun containsKeyWithValueAssertions(
expect: Expect<Map<out String, Int>>,
keyValue: Pair<String, Expect<Int>.() -> Unit>,
otherKeyValues: Array<out Pair<String, Expect<Int>.() -> Unit>>
): Expect<Map<out String, Int>> {
return if (otherKeyValues.isEmpty()) {
expect contains KeyValue(keyValue.first, keyValue.second)
} else {
mapArguments(keyValue, otherKeyValues).to { KeyValue(it.first, it.second) }.let { (first, others) ->
expect contains All(first, *others)
}
}
}
private fun containsKeyWithNullableValueAssertions(
expect: Expect<Map<out String?, Int?>>,
keyValue: Pair<String?, (Expect<Int>.() -> Unit)?>,
otherKeyValues: Array<out Pair<String?, (Expect<Int>.() -> Unit)?>>
): Expect<Map<out String?, Int?>> {
return if (otherKeyValues.isEmpty()) {
expect contains KeyValue(keyValue.first, keyValue.second)
} else {
mapArguments(keyValue, otherKeyValues).to { KeyValue(it.first, it.second) }.let { (first, others) ->
expect contains All(first, *others)
}
}
}
private fun containsKey(expect: Expect<Map<out String, *>>, key: String) = expect containsKey key
private fun containsNullableKey(expect: Expect<Map<out String?, *>>, key: String?) = expect containsKey key
private fun containsNotKey(expect: Expect<Map<out String, *>>, key: String) = expect containsNotKey key
private fun containsNotNullableKey(expect: Expect<Map<out String?, *>>, key: String?) =
expect containsNotKey key
private fun isEmpty(expect: Expect<Map<*, *>>) = expect toBe Empty
private fun isNotEmpty(expect: Expect<Map<*, *>>) = expect notToBe Empty
}
companion object : WithAsciiReporter()
@Suppress("unused", "UNUSED_VALUE")
private fun ambiguityTest() {
@@ -249,3 +190,76 @@ class MapAssertionsSpec : ch.tutteli.atrium.specs.integration.MapAssertionsSpec(
}
}
private fun contains(
expect: Expect<Map<out String, Int>>,
pair: Pair<String, Int>,
otherPairs: Array<out Pair<String, Int>>
): Expect<Map<out String, Int>> {
return if (otherPairs.isEmpty()) {
expect contains (pair.first to pair.second)
} else {
expect contains Pairs(pair, *otherPairs)
}
}
@JvmName("containsNullable")
private fun contains(
expect: Expect<Map<out String?, Int?>>,
pair: Pair<String?, Int?>,
otherPairs: Array<out Pair<String?, Int?>>
): Expect<Map<out String?, Int?>> {
return if (otherPairs.isEmpty()) {
expect contains (pair.first to pair.second)
} else {
expect contains Pairs(pair, *otherPairs)
}
}
@JvmName("containsKeyWithValueAssertions")
private fun contains(
expect: Expect<Map<out String, Int>>,
keyValue: Pair<String, Expect<Int>.() -> Unit>,
otherKeyValues: Array<out Pair<String, Expect<Int>.() -> Unit>>
): Expect<Map<out String, Int>> {
return if (otherKeyValues.isEmpty()) {
expect contains KeyValue(keyValue.first, keyValue.second)
} else {
mapArguments(keyValue, otherKeyValues).to { KeyValue(it.first, it.second) }.let { (first, others) ->
expect contains All(first, *others)
}
}
}
@JvmName("containsKeyWithNullableValueAssertions")
private fun contains(
expect: Expect<Map<out String?, Int?>>,
keyValue: Pair<String?, (Expect<Int>.() -> Unit)?>,
otherKeyValues: Array<out Pair<String?, (Expect<Int>.() -> Unit)?>>
): Expect<Map<out String?, Int?>> {
return if (otherKeyValues.isEmpty()) {
expect contains KeyValue(keyValue.first, keyValue.second)
} else {
mapArguments(keyValue, otherKeyValues).to { KeyValue(it.first, it.second) }.let { (first, others) ->
expect contains All(first, *others)
}
}
}
private fun containsKey(expect: Expect<Map<out String, *>>, key: String) =
expect containsKey key
@JvmName("containsKeyNullable")
private fun containsKey(expect: Expect<Map<out String?, *>>, key: String?) =
expect containsKey key
private fun containsNotKey(expect: Expect<Map<out String, *>>, key: String) =
expect containsNotKey key
@JvmName("containsNotKeyNullable")
private fun containsNotKey(expect: Expect<Map<out String?, *>>, key: String?) =
expect containsNotKey key
private fun isEmpty(expect: Expect<Map<*, *>>) = expect toBe Empty
private fun isNotEmpty(expect: Expect<Map<*, *>>) = expect notToBe Empty

View File

@@ -2,6 +2,8 @@ package ch.tutteli.atrium.api.infix.en_GB
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.specs.*
import ch.tutteli.atrium.specs.testutils.WithAsciiReporter
import kotlin.jvm.JvmName
class MapFeatureAssertionsSpec : ch.tutteli.atrium.specs.integration.MapFeatureAssertionsSpec(
property<Map<String, Int>, Set<String>>(Expect<Map<String, Int>>::keys),
@@ -9,23 +11,11 @@ class MapFeatureAssertionsSpec : ch.tutteli.atrium.specs.integration.MapFeatureA
property<Map<String, Int>, Collection<Int>>(Expect<Map<String, Int>>::values),
fun1<Map<String, Int>, Expect<Collection<Int>>.() -> Unit>(Expect<Map<String, Int>>::values),
feature1<Map<String, Int>, String, Int>(Expect<Map<String, Int>>::getExisting),
fun2<Map<String, Int>, String, Expect<Int>.() -> Unit>(Companion::getExisting),
fun2<Map<String, Int>, String, Expect<Int>.() -> Unit>(::getExisting),
feature1<Map<String?, Int?>, String?, Int?>(Expect<Map<String?, Int?>>::getExisting).withNullableSuffix(),
fun2(Companion::getExisting).name to Companion::getExistingNullable
fun2<Map<String?, Int?>, String?, Expect<Int?>.() -> Unit>(::getExisting).withNullableSuffix()
) {
companion object {
private fun getExisting(
expect: Expect<Map<String, Int>>,
key: String,
assertionCreator: Expect<Int>.() -> Unit
): Expect<Map<String, Int>> = expect getExisting Key(key) assertIt { assertionCreator() }
private fun getExistingNullable(
expect: Expect<Map<String?, Int?>>,
key: String?,
assertionCreator: Expect<Int?>.() -> Unit
): Expect<Map<String?, Int?>> = expect getExisting Key(key) assertIt { assertionCreator() }
}
companion object : WithAsciiReporter()
@Suppress("unused", "UNUSED_VALUE")
private fun ambiguityTest() {
@@ -40,9 +30,22 @@ class MapFeatureAssertionsSpec : ch.tutteli.atrium.specs.integration.MapFeatureA
a3 getExisting null as String?
star getExisting "a"
a1 = a1 getExisting Key("a") assertIt { }
a2 = a2 getExisting Key(1) assertIt { }
a3 = a3 getExisting Key(null) assertIt { }
star = star getExisting Key("a") assertIt { }
a1 = a1 getExisting key("a") { }
a2 = a2 getExisting key(1) { }
a3 = a3 getExisting key(null) { }
star = star getExisting key("a") { }
}
}
private fun getExisting(
expect: Expect<Map<String, Int>>,
key: String,
assertionCreator: Expect<Int>.() -> Unit
): Expect<Map<String, Int>> = expect getExisting key(key) { assertionCreator() }
@JvmName("getExistingNullable")
private fun getExisting(
expect: Expect<Map<String?, Int?>>,
key: String?,
assertionCreator: Expect<Int?>.() -> Unit
): Expect<Map<String?, Int?>> = expect getExisting key(key) { assertionCreator() }

View File

@@ -80,23 +80,16 @@ object NewFeatureAssertionsBuilder : NewFeatureAssertions {
description: Translatable,
provider: T.() -> R
): ExtractedFeaturePostStep<T, R> =
genericFeature(expect, createMetaFeature(expect, description, provider))
genericFeature(expect, ExpectImpl.feature.meta.create(expect, description, provider))
fun <T, R> genericSubjectBasedFeature(
expect: Expect<T>,
provider: (T) -> MetaFeature<R>
): ExtractedFeaturePostStep<T, R> = ExpectImpl.feature.genericFeature(
expect,
expect.maybeSubject.fold(this::createFeatureSubjectNotDefined) { provider(it) }
ExpectImpl.feature.meta.createSubjectBased(expect, provider)
)
private fun <R> createFeatureSubjectNotDefined(): MetaFeature<R> =
MetaFeature(
ErrorMessages.DEDSCRIPTION_BASED_ON_SUBJECT,
RawString.create(ErrorMessages.REPRESENTATION_BASED_ON_SUBJECT_NOT_DEFINED),
None
)
override inline fun <T, R> genericFeature(
expect: Expect<T>,
metaFeature: MetaFeature<R>
@@ -107,30 +100,7 @@ object NewFeatureAssertionsBuilder : NewFeatureAssertions {
description: String,
provider: (T) -> R
): ExtractedFeaturePostStep<T, R> =
genericFeature(expect, createMetaFeature(expect, description, provider))
private fun <T, R> createMetaFeature(
expect: Expect<T>,
description: String,
provider: (T) -> R
): MetaFeature<R> = createMetaFeature(expect, Untranslatable(description), provider)
private fun <T, R> createMetaFeature(
expect: Expect<T>,
description: Translatable,
provider: (T) -> R
): MetaFeature<R> {
return expect.maybeSubject.fold({
MetaFeature(
description,
RawString.create(ErrorMessages.REPRESENTATION_BASED_ON_SUBJECT_NOT_DEFINED),
None
)
}) {
val prop = provider(it)
MetaFeature(description, prop, Some(prop))
}
}
genericFeature(expect, ExpectImpl.feature.meta.create(expect, description, provider))
/**
* Returns [MetaFeatureBuilder] which helps to create a [MetaFeature].
@@ -143,6 +113,7 @@ object NewFeatureAssertionsBuilder : NewFeatureAssertions {
* into an overload ambiguity, then either [p] (for property) or one of the `fN` functions (e.g. [f2] for
* a function which expects 2 arguments).
*/
//TODO move to API, this could potentially be different per API
class MetaFeatureOption<T>(private val expect: Expect<T>) {
/**
@@ -338,4 +309,43 @@ object MetaFeatureBuilder {
fun <A1, A2, A3, A4, A5, R> f5(expect: Expect<*>, f: KFunction5<A1, A2, A3, A4, A5, R>, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) =
MetaFeature(coreFactory.newMethodCallFormatter().formatCall(f.name, arrayOf(a1, a2, a3, a4, a5)), f.invoke(a1, a2, a3, a4, a5))
//@formatter:on
/**
* creates a [MetaFeature] which is entirely based on the subject (i.e. also the description).
*/
fun <T, R> createSubjectBased(
expect: Expect<T>,
provider: (T) -> MetaFeature<R>
): MetaFeature<R> = expect.maybeSubject.fold(this::createFeatureSubjectNotDefined) { provider(it) }
private fun <R> createFeatureSubjectNotDefined(): MetaFeature<R> =
MetaFeature(
ErrorMessages.DEDSCRIPTION_BASED_ON_SUBJECT,
RawString.create(ErrorMessages.REPRESENTATION_BASED_ON_SUBJECT_NOT_DEFINED),
None
)
fun <T, R> create(
expect: Expect<T>,
description: String,
provider: (T) -> R
): MetaFeature<R> = create(expect, Untranslatable(description), provider)
fun <T, R> create(
expect: Expect<T>,
description: Translatable,
provider: (T) -> R
): MetaFeature<R> {
return expect.maybeSubject.fold({
MetaFeature(
description,
RawString.create(ErrorMessages.REPRESENTATION_BASED_ON_SUBJECT_NOT_DEFINED),
None
)
}) {
val feature = provider(it)
MetaFeature(description, feature, Some(feature))
}
}
}

View File

@@ -4,6 +4,7 @@ import ch.tutteli.atrium.core.None
import ch.tutteli.atrium.core.Option
import ch.tutteli.atrium.core.Some
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.creating.FeatureExpect
import ch.tutteli.atrium.domain.builders.creating.changers.FeatureExtractorBuilder
import ch.tutteli.atrium.domain.builders.creating.changers.FeatureOptions
import ch.tutteli.atrium.domain.creating.changers.ExtractedFeaturePostStep
@@ -68,13 +69,13 @@ class FinalStepImpl<T, R>(
extractAndApply = { assertionCreator -> extractIt(this, Some(assertionCreator)) }
)
private fun extractIt(expect: Expect<T>, subAssertions: Option<Expect<R>.() -> Unit>) =
private fun extractIt(expect: Expect<T>, maybeSubAssertions: Option<Expect<R>.() -> Unit>): FeatureExpect<T, R> =
featureExtractor.extract(
expect,
featureOptions?.description ?: featureExtractionStep.description,
featureExtractionStep.representationForFailure,
featureExtraction,
subAssertions,
maybeSubAssertions,
featureOptions?.representationInsteadOfFeature
)
}

View File

@@ -19,7 +19,7 @@ interface VarArgHelper<out T> {
/**
* Creates an [ArgumentMapperBuilder] which allows to map [expected] and [otherExpected].
*/
val mapArguments get() = ArgumentMapperBuilder(expected, otherExpected)
val mapArguments: ArgumentMapperBuilder<T> get() = ArgumentMapperBuilder(expected, otherExpected)
/**
* Returns the arguments as [List].