Skip to content

Commit

Permalink
Make Iterable.all and Iterable.containsNoDuplicates use hasNext (#939)
Browse files Browse the repository at this point in the history
  • Loading branch information
wordhou committed Jul 6, 2021
1 parent ce5d124 commit 1a8e024
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 158 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2206,8 +2206,11 @@ expect(Person("Susanne", "Whitley", 43, listOf()))
```text
expected that subject: Person(firstName=Susanne, lastName=Whitley, age=43, children=[]) (readme.examples.Person <1234789>)
◆ ▶ children: [] (kotlin.collections.EmptyList <1234789>)
◾ ▶ has at least one element: false
◾ is: true
◾ has: a next element
» all entries:
» ▶ age:
» ▶ age:
◾ is greater than or equal to: 18 (kotlin.Int <1234789>)
```
</ex-own-compose-4>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ package ch.tutteli.atrium.logic.creating.basic.contains.creators.impl

import ch.tutteli.atrium.assertions.Assertion
import ch.tutteli.atrium.assertions.AssertionGroup
import ch.tutteli.atrium.assertions.builders.assertionBuilder
import ch.tutteli.atrium.assertions.builders.invisibleGroup
import ch.tutteli.atrium.core.trueProvider
import ch.tutteli.atrium.creating.AssertionContainer
import ch.tutteli.atrium.logic.creating.basic.contains.Contains
import ch.tutteli.atrium.logic.creating.iterable.contains.searchbehaviours.NotSearchBehaviour
import ch.tutteli.atrium.logic.impl.createAssertionGroupFromListOfAssertions
import ch.tutteli.atrium.logic.impl.createExplanatoryGroupForMismatches
import ch.tutteli.atrium.reporting.translating.Translatable

Expand Down Expand Up @@ -50,15 +48,7 @@ abstract class ContainsObjectsAssertionCreator<T : Any, TT : Any, in SC, S : Con
assertions.add(featureAssertion)
}

return if (assertions.isEmpty()) {
assertionBuilder.invisibleGroup
.withAssertion(
assertionBuilder.createDescriptive(groupDescription, searchCriterion, trueProvider)
).build()
} else assertionBuilder.list
.withDescriptionAndRepresentation(groupDescription, searchCriterion)
.withAssertions(assertions)
.build()
return createAssertionGroupFromListOfAssertions(groupDescription, searchCriterion, assertions)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package ch.tutteli.atrium.logic.creating.iterable.contains.creators.impl
import ch.tutteli.atrium.assertions.Assertion
import ch.tutteli.atrium.assertions.AssertionGroup
import ch.tutteli.atrium.assertions.builders.assertionBuilder
import ch.tutteli.atrium.assertions.builders.invisibleGroup
import ch.tutteli.atrium.core.None
import ch.tutteli.atrium.core.getOrElse
import ch.tutteli.atrium.creating.AssertionContainer
Expand All @@ -16,15 +15,10 @@ import ch.tutteli.atrium.logic.creating.iterable.contains.IterableLikeContains
import ch.tutteli.atrium.logic.creating.iterable.contains.searchbehaviours.InAnyOrderSearchBehaviour
import ch.tutteli.atrium.logic.creating.iterable.contains.searchbehaviours.NotSearchBehaviour
import ch.tutteli.atrium.logic.creating.typeutils.IterableLike
import ch.tutteli.atrium.logic.hasNext
import ch.tutteli.atrium.logic.impl.allCreatedAssertionsHold
import ch.tutteli.atrium.logic.impl.createExplanatoryAssertionGroup
import ch.tutteli.atrium.logic.impl.createExplanatoryGroupForMismatches
import ch.tutteli.atrium.logic.impl.createIndexAssertions
import ch.tutteli.atrium.logic.impl.*
import ch.tutteli.atrium.reporting.translating.Translatable
import ch.tutteli.atrium.translations.DescriptionIterableAssertion
import ch.tutteli.atrium.translations.DescriptionIterableAssertion.AN_ELEMENT_WHICH
import ch.tutteli.kbox.identity

/**
* Represents a creator of a sophisticated `contains` assertions for [Iterable] where an expected entry can appear
Expand Down Expand Up @@ -63,20 +57,9 @@ class InAnyOrderEntriesAssertionCreator<E : Any, T : IterableLike>(
inAnyOrderAssertion: AssertionGroup,
multiConsumableContainer: AssertionContainer<List<E?>>
): AssertionGroup {
val hasNext = multiConsumableContainer.hasNext(::identity)
return if (searchBehaviour is NotSearchBehaviour && !hasNext.holds()) {
assertionBuilder.invisibleGroup
.withAssertions(
hasNext,
assertionBuilder.explanatoryGroup
.withDefaultType
.withAssertion(inAnyOrderAssertion)
.build()
)
.build()
} else {
inAnyOrderAssertion
}
return if (searchBehaviour is NotSearchBehaviour)
decorateAssertionWithHasNext(inAnyOrderAssertion, multiConsumableContainer)
else inAnyOrderAssertion
}

override fun searchAndCreateAssertion(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@ package ch.tutteli.atrium.logic.creating.iterable.contains.creators.impl

import ch.tutteli.atrium.assertions.Assertion
import ch.tutteli.atrium.assertions.AssertionGroup
import ch.tutteli.atrium.assertions.builders.assertionBuilder
import ch.tutteli.atrium.assertions.builders.invisibleGroup
import ch.tutteli.atrium.core.getOrElse
import ch.tutteli.atrium.creating.AssertionContainer
import ch.tutteli.atrium.logic.creating.basic.contains.creators.impl.ContainsObjectsAssertionCreator
import ch.tutteli.atrium.logic.creating.iterable.contains.IterableLikeContains
import ch.tutteli.atrium.logic.creating.iterable.contains.searchbehaviours.InAnyOrderSearchBehaviour
import ch.tutteli.atrium.logic.creating.iterable.contains.searchbehaviours.NotSearchBehaviour
import ch.tutteli.atrium.logic.creating.typeutils.IterableLike
import ch.tutteli.atrium.logic.hasNext
import ch.tutteli.atrium.logic.impl.createExplanatoryGroupForMismatches
import ch.tutteli.atrium.logic.impl.createIndexAssertions
import ch.tutteli.atrium.logic.impl.decorateAssertionWithHasNext
import ch.tutteli.atrium.reporting.translating.Translatable
import ch.tutteli.atrium.translations.DescriptionIterableAssertion
import ch.tutteli.kbox.identity

/**
* Represents a creator of a sophisticated `contains` assertions for [Iterable] where an expected entry can appear
Expand Down Expand Up @@ -71,17 +67,8 @@ class InAnyOrderValuesAssertionCreator<SC, T : IterableLike>(
inAnyOrderAssertion: AssertionGroup,
multiConsumableContainer: AssertionContainer<List<SC>>
): AssertionGroup {
val hasNext = multiConsumableContainer.hasNext(::identity)
return if (searchBehaviour is NotSearchBehaviour && !hasNext.holds()) {
assertionBuilder.invisibleGroup.withAssertions(
hasNext,
assertionBuilder.explanatoryGroup
.withDefaultType
.withAssertion(inAnyOrderAssertion)
.build()
).build()
} else {
inAnyOrderAssertion
}
return if (searchBehaviour is NotSearchBehaviour)
decorateAssertionWithHasNext(inAnyOrderAssertion, multiConsumableContainer)
else inAnyOrderAssertion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package ch.tutteli.atrium.logic.impl
import ch.tutteli.atrium.assertions.Assertion
import ch.tutteli.atrium.assertions.builders.*
import ch.tutteli.atrium.core.Option
import ch.tutteli.atrium.core.getOrElse
import ch.tutteli.atrium.creating.AssertionContainer
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.logic.IterableLikeAssertions
import ch.tutteli.atrium.logic._logic
import ch.tutteli.atrium.logic.*
import ch.tutteli.atrium.logic.assertions.impl.LazyThreadUnsafeAssertionGroup
import ch.tutteli.atrium.logic.createDescriptiveAssertion
import ch.tutteli.atrium.logic.creating.iterable.contains.IterableLikeContains
import ch.tutteli.atrium.logic.creating.iterable.contains.creators.impl.turnSubjectToList
import ch.tutteli.atrium.logic.creating.iterable.contains.searchbehaviours.NoOpSearchBehaviour
import ch.tutteli.atrium.logic.creating.iterable.contains.searchbehaviours.NotSearchBehaviour
import ch.tutteli.atrium.logic.creating.iterable.contains.searchbehaviours.impl.NoOpSearchBehaviourImpl
Expand All @@ -19,9 +19,6 @@ import ch.tutteli.atrium.logic.creating.iterable.contains.steps.impl.EntryPointS
import ch.tutteli.atrium.logic.creating.iterable.contains.steps.notCheckerStep
import ch.tutteli.atrium.logic.creating.transformers.FeatureExtractorBuilder
import ch.tutteli.atrium.logic.creating.typeutils.IterableLike
import ch.tutteli.atrium.logic.extractFeature
import ch.tutteli.atrium.reporting.Text
import ch.tutteli.atrium.reporting.translating.Translatable
import ch.tutteli.atrium.reporting.translating.TranslatableWithArgs
import ch.tutteli.atrium.translations.DescriptionBasic
import ch.tutteli.atrium.translations.DescriptionIterableAssertion
Expand Down Expand Up @@ -91,60 +88,31 @@ class DefaultIterableLikeAssertions : IterableLikeAssertions {
converter: (T) -> Iterable<E?>,
assertionCreatorOrNull: (Expect<E>.() -> Unit)?
): Assertion = LazyThreadUnsafeAssertionGroup {
val list = transformToList(container, converter)

val assertions = ArrayList<Assertion>(2)
assertions.add(createExplanatoryAssertionGroup(container, assertionCreatorOrNull))
val listAssertionContainer = turnSubjectToList(container, converter)
val list = listAssertionContainer.maybeSubject.getOrElse { emptyList() }

val explanatoryGroup = createExplanatoryAssertionGroup(container, assertionCreatorOrNull)
val assertions = mutableListOf<Assertion>(explanatoryGroup)
val mismatches = createIndexAssertions(list) { (_, element) ->
!allCreatedAssertionsHold(container, element, assertionCreatorOrNull)
}
assertions.add(createExplanatoryGroupForMismatches(mismatches))

createHasElementPlusFixedClaimGroup(
list,
DescriptionIterableAssertion.ALL,
Text.EMPTY,
mismatches.isEmpty(),
assertions
)
}
if (mismatches.isNotEmpty()) assertions.add(createExplanatoryGroupForMismatches(mismatches))

private fun <T : IterableLike, E> transformToList(
container: AssertionContainer<T>,
converter: (T) -> Iterable<E>
): List<E> =
container.maybeSubject.fold({ emptyList() }) { subject ->
val iterable = converter(subject)
when (iterable) {
is List<E> -> iterable
else -> iterable.toList()
}
}

private fun <E> createHasElementPlusFixedClaimGroup(
list: List<E>,
description: Translatable,
representation: IterableLike,
claim: Boolean,
assertions: List<Assertion>
) = assertionBuilder.invisibleGroup
.withAssertions(
createHasElementAssertion(list),
assertionBuilder.fixedClaimGroup
.withListType
.withClaim(claim)
.withDescriptionAndRepresentation(description, representation)
decorateAssertionWithHasNext(
assertionBuilder.list
.withDescriptionAndEmptyRepresentation(DescriptionIterableAssertion.ALL)
.withAssertions(assertions)
.build()
.build(),
listAssertionContainer
)
.build()
}

override fun <T : IterableLike, E> containsNoDuplicates(
container: AssertionContainer<T>,
converter: (T) -> Iterable<E>
): Assertion = LazyThreadUnsafeAssertionGroup {
val list = transformToList(container, converter)
val listAssertionContainer = turnSubjectToList(container, converter)
val list = listAssertionContainer.maybeSubject.getOrElse { emptyList() }

val lookupHashMap = HashMap<E, Int>()
val duplicateIndices = HashMap<Int, Pair<E, MutableList<Int>>>()
Expand All @@ -163,34 +131,35 @@ class DefaultIterableLikeAssertions : IterableLikeAssertions {

val duplicates = duplicateIndices
.map { (index, pair) ->
pair.let { (element, duplicateIndices) ->
assertionBuilder.descriptive
.failing
.withFailureHint {
assertionBuilder.explanatoryGroup
.withDefaultType
.withExplanatoryAssertion(
TranslatableWithArgs(
DescriptionIterableAssertion.DUPLICATED_BY,
duplicateIndices.joinToString(", ")
)
val (element, indices) = pair
assertionBuilder.descriptive
.failing
.withFailureHint {
assertionBuilder.explanatoryGroup
.withDefaultType
.withExplanatoryAssertion(
TranslatableWithArgs(
DescriptionIterableAssertion.DUPLICATED_BY,
indices.joinToString(", ")
)
.build()
}
.showForAnyFailure
.withDescriptionAndRepresentation(
TranslatableWithArgs(DescriptionIterableAssertion.INDEX, index),
element
)
.build()
}
)
.build()
}
.showForAnyFailure
.withDescriptionAndRepresentation(
TranslatableWithArgs(DescriptionIterableAssertion.INDEX, index),
element
)
.build()
}

createHasElementPlusFixedClaimGroup(
list,
DescriptionBasic.HAS_NOT, DescriptionIterableAssertion.DUPLICATE_ELEMENTS,
duplicates.isEmpty(),
duplicates
decorateAssertionWithHasNext(
createAssertionGroupFromListOfAssertions(
DescriptionBasic.HAS_NOT,
DescriptionIterableAssertion.DUPLICATE_ELEMENTS,
duplicates
),
listAssertionContainer
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ch.tutteli.atrium.logic.impl
import ch.tutteli.atrium.assertions.Assertion
import ch.tutteli.atrium.assertions.AssertionGroup
import ch.tutteli.atrium.assertions.builders.assertionBuilder
import ch.tutteli.atrium.assertions.builders.invisibleGroup
import ch.tutteli.atrium.core.None
import ch.tutteli.atrium.core.Some
import ch.tutteli.atrium.core.falseProvider
Expand All @@ -11,22 +12,16 @@ import ch.tutteli.atrium.creating.AssertionContainer
import ch.tutteli.atrium.creating.Expect
import ch.tutteli.atrium.logic.collectBasedOnSubject
import ch.tutteli.atrium.logic.creating.collectors.collectAssertions
import ch.tutteli.atrium.logic.hasNext
import ch.tutteli.atrium.reporting.Text
import ch.tutteli.atrium.reporting.translating.Translatable
import ch.tutteli.atrium.reporting.translating.TranslatableWithArgs
import ch.tutteli.atrium.translations.DescriptionBasic
import ch.tutteli.atrium.translations.DescriptionIterableAssertion
import ch.tutteli.kbox.WithIndex
import ch.tutteli.kbox.identity
import ch.tutteli.kbox.mapWithIndex

internal fun createHasElementAssertion(list: List<*>): Assertion {
return assertionBuilder.feature
.withDescriptionAndRepresentation(DescriptionIterableAssertion.HAS_ELEMENT, Text(list.isNotEmpty().toString()))
.withAssertion(
assertionBuilder.createDescriptive(DescriptionBasic.IS, Text(true.toString())) { list.isNotEmpty() }
)
.build()
}

internal fun <E : Any> allCreatedAssertionsHold(
container: AssertionContainer<*>,
subject: E?,
Expand Down Expand Up @@ -76,7 +71,7 @@ internal fun <E> createIndexAssertions(

internal fun createExplanatoryGroupForMismatches(
mismatches: List<Assertion>
) : AssertionGroup {
): AssertionGroup {
return assertionBuilder.explanatoryGroup
.withWarningType
.withAssertion(
Expand All @@ -88,3 +83,39 @@ internal fun createExplanatoryGroupForMismatches(
.failing
.build()
}

internal fun createAssertionGroupFromListOfAssertions(
description: Translatable,
representation: Any?,
assertions: List<Assertion>
): AssertionGroup =
if (assertions.isEmpty())
assertionBuilder.invisibleGroup
.withAssertion(
assertionBuilder.createDescriptive(description, representation, trueProvider)
).build()
else assertionBuilder.list
.withDescriptionAndRepresentation(description, representation)
.withAssertions(assertions)
.build()

internal fun <E> decorateAssertionWithHasNext(
assertion: AssertionGroup,
listAssertionContainer: AssertionContainer<List<E>>
): AssertionGroup {
val hasNext = listAssertionContainer.hasNext(::identity)
return if (hasNext.holds()) {
assertion
} else {
assertionBuilder.invisibleGroup
.withAssertions(
hasNext,
assertionBuilder.explanatoryGroup
.withDefaultType
.withAssertion(assertion)
.build()
)
.build()
}
}

Loading

0 comments on commit 1a8e024

Please sign in to comment.