diff --git a/README.md b/README.md index 5c025294c..ad433d09c 100644 --- a/README.md +++ b/README.md @@ -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>) ``` diff --git a/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/creating/basic/contains/creators/impl/ContainsObjectsAssertionCreator.kt b/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/creating/basic/contains/creators/impl/ContainsObjectsAssertionCreator.kt index d16f6b800..00fd6d3a1 100644 --- a/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/creating/basic/contains/creators/impl/ContainsObjectsAssertionCreator.kt +++ b/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/creating/basic/contains/creators/impl/ContainsObjectsAssertionCreator.kt @@ -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 @@ -50,15 +48,7 @@ abstract class ContainsObjectsAssertionCreator( inAnyOrderAssertion: AssertionGroup, multiConsumableContainer: AssertionContainer> ): 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( diff --git a/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/creating/iterable/contains/creators/impl/InAnyOrderValuesAssertionCreator.kt b/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/creating/iterable/contains/creators/impl/InAnyOrderValuesAssertionCreator.kt index 865714ecb..9c83233d6 100644 --- a/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/creating/iterable/contains/creators/impl/InAnyOrderValuesAssertionCreator.kt +++ b/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/creating/iterable/contains/creators/impl/InAnyOrderValuesAssertionCreator.kt @@ -2,8 +2,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.getOrElse import ch.tutteli.atrium.creating.AssertionContainer import ch.tutteli.atrium.logic.creating.basic.contains.creators.impl.ContainsObjectsAssertionCreator @@ -11,12 +9,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.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 @@ -71,17 +67,8 @@ class InAnyOrderValuesAssertionCreator( inAnyOrderAssertion: AssertionGroup, multiConsumableContainer: AssertionContainer> ): 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 } } diff --git a/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/impl/DefaultIterableLikeAssertions.kt b/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/impl/DefaultIterableLikeAssertions.kt index 6fadab555..f4945aae1 100644 --- a/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/impl/DefaultIterableLikeAssertions.kt +++ b/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/impl/DefaultIterableLikeAssertions.kt @@ -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 @@ -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 @@ -91,60 +88,31 @@ class DefaultIterableLikeAssertions : IterableLikeAssertions { converter: (T) -> Iterable, assertionCreatorOrNull: (Expect.() -> Unit)? ): Assertion = LazyThreadUnsafeAssertionGroup { - val list = transformToList(container, converter) - - val assertions = ArrayList(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(explanatoryGroup) val mismatches = createIndexAssertions(list) { (_, element) -> !allCreatedAssertionsHold(container, element, assertionCreatorOrNull) } - assertions.add(createExplanatoryGroupForMismatches(mismatches)) + if (mismatches.isNotEmpty()) assertions.add(createExplanatoryGroupForMismatches(mismatches)) - createHasElementPlusFixedClaimGroup( - list, - DescriptionIterableAssertion.ALL, - Text.EMPTY, - mismatches.isEmpty(), - assertions + decorateAssertionWithHasNext( + assertionBuilder.list + .withDescriptionAndEmptyRepresentation(DescriptionIterableAssertion.ALL) + .withAssertions(assertions) + .build(), + listAssertionContainer ) } - private fun transformToList( - container: AssertionContainer, - converter: (T) -> Iterable - ): List = - container.maybeSubject.fold({ emptyList() }) { subject -> - val iterable = converter(subject) - when (iterable) { - is List -> iterable - else -> iterable.toList() - } - } - - private fun createHasElementPlusFixedClaimGroup( - list: List, - description: Translatable, - representation: IterableLike, - claim: Boolean, - assertions: List - ) = assertionBuilder.invisibleGroup - .withAssertions( - createHasElementAssertion(list), - assertionBuilder.fixedClaimGroup - .withListType - .withClaim(claim) - .withDescriptionAndRepresentation(description, representation) - .withAssertions(assertions) - .build() - ) - .build() - override fun containsNoDuplicates( container: AssertionContainer, converter: (T) -> Iterable ): Assertion = LazyThreadUnsafeAssertionGroup { - val list = transformToList(container, converter) + val listAssertionContainer = turnSubjectToList(container, converter) + val list = listAssertionContainer.maybeSubject.getOrElse { emptyList() } val lookupHashMap = HashMap() val duplicateIndices = HashMap>>() @@ -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 ) } } diff --git a/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/impl/containsHelpers.kt b/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/impl/containsHelpers.kt index 7b03327df..186d80b0a 100644 --- a/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/impl/containsHelpers.kt +++ b/logic/atrium-logic-common/src/main/kotlin/ch/tutteli/atrium/logic/impl/containsHelpers.kt @@ -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 @@ -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 allCreatedAssertionsHold( container: AssertionContainer<*>, subject: E?, @@ -76,7 +71,7 @@ internal fun createIndexAssertions( internal fun createExplanatoryGroupForMismatches( mismatches: List -) : AssertionGroup { +): AssertionGroup { return assertionBuilder.explanatoryGroup .withWarningType .withAssertion( @@ -88,3 +83,39 @@ internal fun createExplanatoryGroupForMismatches( .failing .build() } + +internal fun createAssertionGroupFromListOfAssertions( + description: Translatable, + representation: Any?, + assertions: List +): AssertionGroup = + if (assertions.isEmpty()) + assertionBuilder.invisibleGroup + .withAssertion( + assertionBuilder.createDescriptive(description, representation, trueProvider) + ).build() + else assertionBuilder.list + .withDescriptionAndRepresentation(description, representation) + .withAssertions(assertions) + .build() + +internal fun decorateAssertionWithHasNext( + assertion: AssertionGroup, + listAssertionContainer: AssertionContainer> +): AssertionGroup { + val hasNext = listAssertionContainer.hasNext(::identity) + return if (hasNext.holds()) { + assertion + } else { + assertionBuilder.invisibleGroup + .withAssertions( + hasNext, + assertionBuilder.explanatoryGroup + .withDefaultType + .withAssertion(assertion) + .build() + ) + .build() + } +} + diff --git a/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/IterableExpectationsSpec.kt b/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/IterableExpectationsSpec.kt index 6a66cd0e1..01cc9e500 100644 --- a/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/IterableExpectationsSpec.kt +++ b/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/IterableExpectationsSpec.kt @@ -44,6 +44,8 @@ abstract class IterableExpectationsSpec( val nextElementDescr = DescriptionIterableAssertion.NEXT_ELEMENT.getDefault() val duplicateElements = DescriptionIterableAssertion.DUPLICATE_ELEMENTS.getDefault() + val hasANextElement = "$hasDescr: $nextElementDescr" + describeFun(toHaveANextElement) { val toHaveANextElementFun = toHaveANextElement.lambda @@ -54,7 +56,7 @@ abstract class IterableExpectationsSpec( it("throws an AssertionError if an iterable does not have next") { expect { expect(listOf() as Iterable).toHaveANextElementFun() - }.toThrow { messageToContain("$hasDescr: $nextElementDescr") } + }.toThrow { messageToContain(hasANextElement) } } } @@ -132,24 +134,43 @@ abstract class IterableExpectationsSpec( describeFun(notToContainDuplicates) { val notToContainDuplicatesFun = notToContainDuplicates.lambda - it("list without duplicates") { - expect(listOf(1, 2) as Iterable).notToContainDuplicatesFun() + describe("empty collection") { + it("throws AssertionError as there needs to be at least one element") { + expect { + expect(listOf() as Iterable).notToContainDuplicatesFun() + }.toThrow { + message { + toContain( + "$hasANextElement", + "$hasNotDescr: $duplicateElements" + ) + } + } + } } - it("list with duplicates") { - fun index(i: Int, element: Int) = DescriptionIterableAssertion.INDEX.getDefault().format("$i: $element") - fun duplicatedBy(vararg elements: Int) = - DescriptionIterableAssertion.DUPLICATED_BY.getDefault().format(elements.joinToString(", ")) + describe("list without duplicates") { + it("happy case") { + expect(listOf(1, 2) as Iterable).notToContainDuplicatesFun() + } + } - val input = listOf(1, 2, 1, 2, 3, 4, 4, 4).asIterable() + describe("list with duplicates") { + it("throws AssertionError with details of duplicate indices") { + fun index(i: Int, element: Int) = DescriptionIterableAssertion.INDEX.getDefault().format("$i: $element") + fun duplicatedBy(vararg elements: Int) = + DescriptionIterableAssertion.DUPLICATED_BY.getDefault().format(elements.joinToString(", ")) - expect { - expect(input).notToContainDuplicatesFun() - }.toThrow { - message { - toContain("$hasNotDescr: $duplicateElements") - toContain(index(0, 1), index(1, 2), index(5, 4)) - toContain(duplicatedBy(2), duplicatedBy(3), duplicatedBy(6, 7)) + val input = listOf(1, 2, 1, 2, 3, 4, 4, 4).asIterable() + + expect { + expect(input).notToContainDuplicatesFun() + }.toThrow { + message { + toContain("$hasNotDescr: $duplicateElements") + toContain(index(0, 1), index(1, 2), index(5, 4)) + toContain(duplicatedBy(2), duplicatedBy(3), duplicatedBy(6, 7)) + } } } } diff --git a/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/IterableToHaveElementsAndAllExpectationsSpec.kt b/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/IterableToHaveElementsAndAllExpectationsSpec.kt index 2fda0a34f..0f017f8b2 100644 --- a/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/IterableToHaveElementsAndAllExpectationsSpec.kt +++ b/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/IterableToHaveElementsAndAllExpectationsSpec.kt @@ -30,7 +30,6 @@ abstract class IterableToHaveElementsAndAllExpectationsSpec( ) {}) val toHaveElementsAndAllDescr = DescriptionIterableAssertion.ALL.getDefault() - val hasElement = DescriptionIterableAssertion.HAS_ELEMENT.getDefault() val explanatoryPointWithIndent = "$indentRootBulletPoint$indentListBulletPoint$explanatoryBulletPoint" @@ -47,9 +46,10 @@ abstract class IterableToHaveElementsAndAllExpectationsSpec( expect { expect(fluentEmpty()).toHaveElementsAndAllFun { toBeLessThan(1.0) } }.toThrow { - messageToContain( - "$rootBulletPoint$featureArrow$hasElement: false$separator" + - "$indentRootBulletPoint$indentFeatureArrow$featureBulletPoint$isDescr: true" + message.toContainRegex( + "$hasANextElement", + "$explanatoryBulletPoint$toHaveElementsAndAllDescr: ", + "$explanatoryPointWithIndent$toBeLessThanDescr: 1.0" ) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index f4b1bf70b..3b9da9812 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -40,7 +40,9 @@ buildscript { or( "(CharSequence|Iterable)Contains.*Spec", "IterableAnyAssertionsSpec" - ) + ".*`.*(any|contains).*`.*(throws.*AssertionError|failing cases)" + ) + ".*`.*(any|contains).*`.*(throws.*AssertionError|failing cases)", + // changed reporting for Iterable.all empty collection cases with 0.17.0 + "IterableAllAssertionsSpec.*" + "empty collection.*" + "throws AssertionError" ) + ".*)", // we don't use asci bullet points in reporting since 0.17.0 // but have own tests to assure that changing bullet points work @@ -108,7 +110,9 @@ buildscript { or( "(CharSequence|Iterable)Contains.*Spec", "IterableAnyAssertionsSpec" - ) + ".*`.*(any|contains).*`.*(throws.*AssertionError|failing cases)" + ) + ".*`.*(any|contains).*`.*(throws.*AssertionError|failing cases)", + // changed reporting for Iterable.all empty collection cases with 0.17.0 + "IterableAllAssertionsSpec.*" + "empty collection.*" + "throws AssertionError" ) + ".*)", // we don't use asci bullet points in reporting since 0.17.0 // but have own tests to assure that changing bullet points work @@ -177,7 +181,9 @@ buildscript { or( "(CharSequence|Iterable)Contains.*Spec", "IterableAnyExpectationsSpec" - ) + ".*`.*(any|contains).*`.*(throws.*AssertionError|failing cases)" + ) + ".*`.*(any|contains).*`.*(throws.*AssertionError|failing cases)", + // changed reporting for Iterable.all empty collection cases with 0.17.0 + "IterableAllExpectationsSpec.*" + "empty collection.*" + "throws AssertionError" ) + ".*)").let { commonPatterns -> Pair( // bc