diff --git a/kotest-assertions/kotest-assertions-core/api/kotest-assertions-core.api b/kotest-assertions/kotest-assertions-core/api/kotest-assertions-core.api index 2cef126dabb..5d0c3946fe4 100644 --- a/kotest-assertions/kotest-assertions-core/api/kotest-assertions-core.api +++ b/kotest-assertions/kotest-assertions-core/api/kotest-assertions-core.api @@ -341,6 +341,7 @@ public final class io/kotest/matchers/collections/CollectionMatchersKt { public static final fun existInOrder ([Lkotlin/jvm/functions/Function1;)Lio/kotest/matchers/Matcher; public static final fun haveSize (I)Lio/kotest/matchers/Matcher; public static final fun matchEach (Ljava/util/List;)Lio/kotest/matchers/Matcher; + public static final fun matchEach (Ljava/util/List;Lkotlin/jvm/functions/Function2;)Lio/kotest/matchers/Matcher; public static final fun matchEach ([Lkotlin/jvm/functions/Function1;)Lio/kotest/matchers/Matcher; public static final fun matchInOrder (Ljava/util/List;Z)Lio/kotest/matchers/Matcher; public static final fun matchInOrder ([Lkotlin/jvm/functions/Function1;)Lio/kotest/matchers/Matcher; @@ -593,9 +594,11 @@ public final class io/kotest/matchers/collections/MatchersKt { public static final fun shouldHaveElementAt (Ljava/lang/Iterable;ILjava/lang/Object;)V public static final fun shouldHaveElementAt (Ljava/util/List;ILjava/lang/Object;)V public static final fun shouldHaveElementAt ([Ljava/lang/Object;ILjava/lang/Object;)V + public static final fun shouldMatchEach (Ljava/lang/Iterable;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)V public static final fun shouldMatchEach (Ljava/lang/Iterable;Ljava/util/List;)V public static final fun shouldMatchEach (Ljava/lang/Iterable;[Lkotlin/jvm/functions/Function1;)V public static final fun shouldMatchEach (Ljava/util/List;Ljava/util/List;)V + public static final fun shouldMatchEach (Ljava/util/List;Ljava/util/List;Lkotlin/jvm/functions/Function2;)V public static final fun shouldMatchEach (Ljava/util/List;[Lkotlin/jvm/functions/Function1;)V public static final fun shouldMatchEach ([Ljava/lang/Object;Ljava/util/List;)V public static final fun shouldMatchEach ([Ljava/lang/Object;[Lkotlin/jvm/functions/Function1;)V @@ -2142,6 +2145,9 @@ public final class io/kotest/matchers/sequences/MatchersKt { public static final fun shouldHaveSingleElement (Lkotlin/sequences/Sequence;Ljava/lang/Object;)V public static final fun shouldHaveSize (Lkotlin/sequences/Sequence;I)V public static final fun shouldHaveUpperBound (Lkotlin/sequences/Sequence;Ljava/lang/Comparable;)V + public static final fun shouldMatchEach (Lkotlin/sequences/Sequence;Ljava/util/List;)V + public static final fun shouldMatchEach (Lkotlin/sequences/Sequence;Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function2;)V + public static final fun shouldMatchEach (Lkotlin/sequences/Sequence;[Lkotlin/jvm/functions/Function1;)V public static final fun shouldNotBeEmpty (Lkotlin/sequences/Sequence;)V public static final fun shouldNotBeSorted (Lkotlin/sequences/Sequence;)V public static final fun shouldNotBeSortedWith (Lkotlin/sequences/Sequence;Ljava/util/Comparator;)V diff --git a/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/collections/CollectionMatchers.kt b/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/collections/CollectionMatchers.kt index c6854c922dc..3560fae3b62 100644 --- a/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/collections/CollectionMatchers.kt +++ b/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/collections/CollectionMatchers.kt @@ -164,12 +164,23 @@ fun matchInOrder(assertions: List<(T) -> Unit>, allowGaps: Boolean): Matcher } } +/** + * Asserts that each element of the collection matches with the element of [expected]. + * Elements will be compared sequentially by passing the actual / expected pairs to the + * [asserter] in the order given by the iterators of the collections. + */ +fun matchEach(expected: List, asserter: (T, T) -> Unit): Matcher?> = + matchEach(expected.map { expectedElement -> + { actualElement: T -> + asserter(actualElement, expectedElement) + } + }) + /** * Asserts that each element in the collection matches its corresponding matcher in [assertions]. * Elements will be compared sequentially in the order given by the iterators of the collections. */ fun matchEach(assertions: List<(T) -> Unit>): Matcher?> = neverNullMatcher { actual -> - data class ElementPass(val atIndex: Int) data class MatchEachProblem(val atIndex: Int, val problem: String?) val problems = errorCollector.runWithMode(ErrorCollectionMode.Hard) { @@ -187,7 +198,7 @@ fun matchEach(assertions: List<(T) -> Unit>): Matcher?> = neve } } } - } + (actual.size..assertions.size - 1).map { + } + (actual.size until assertions.size).map { MatchEachProblem( it, "No actual element for assertion at index $it" @@ -198,7 +209,7 @@ fun matchEach(assertions: List<(T) -> Unit>): Matcher?> = neve problems.isEmpty(), { "Expected each element to pass its assertion, but found issues at indexes: [${problems.joinToString { it.atIndex.toString() }}]\n\n" + - "${problems.joinToString(separator = "\n") { "${it.atIndex} => ${it.problem}" }}" + problems.joinToString(separator = "\n") { "${it.atIndex} => ${it.problem}" } }, { "Expected some element to fail its assertion, but all passed." }, ) diff --git a/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/collections/matchers.kt b/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/collections/matchers.kt index 709bde1e8c9..83b9cf88527 100644 --- a/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/collections/matchers.kt +++ b/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/collections/matchers.kt @@ -75,8 +75,10 @@ fun Iterable.shouldMatchEach(vararg assertions: (T) -> Unit) = toList().s fun Array.shouldMatchEach(vararg assertions: (T) -> Unit) = asList().shouldMatchEach(assertions.toList()) fun List.shouldMatchEach(vararg assertions: (T) -> Unit) = this.shouldMatchEach(assertions.toList()) infix fun Iterable.shouldMatchEach(assertions: List<(T) -> Unit>) = toList().shouldMatchEach(assertions) +fun Iterable.shouldMatchEach(expected: Iterable, asserter: (T, T) -> Unit) = toList().shouldMatchEach(expected.toList(), asserter) infix fun Array.shouldMatchEach(assertions: List<(T) -> Unit>) = asList().shouldMatchEach(assertions) infix fun List.shouldMatchEach(assertions: List<(T) -> Unit>) = this should matchEach(assertions.toList()) +fun List.shouldMatchEach(expected: List, asserter: (T, T) -> Unit) = this should matchEach(expected, asserter) fun Iterable.shouldNotMatchEach(vararg assertions: (T) -> Unit) = toList().shouldNotMatchEach(assertions.toList()) fun Array.shouldNotMatchEach(vararg assertions: (T) -> Unit) = asList().shouldNotMatchEach(assertions.toList()) fun List.shouldNotMatchEach(vararg assertions: (T) -> Unit) = this.shouldNotMatchEach(assertions.toList()) diff --git a/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/sequences/matchers.kt b/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/sequences/matchers.kt index 166b993a2a1..30b8a5d239b 100644 --- a/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/sequences/matchers.kt +++ b/kotest-assertions/kotest-assertions-core/src/commonMain/kotlin/io/kotest/matchers/sequences/matchers.kt @@ -4,8 +4,14 @@ package io.kotest.matchers.sequences import io.kotest.assertions.eq.eq import io.kotest.assertions.print.print -import io.kotest.matchers.* +import io.kotest.matchers.Matcher +import io.kotest.matchers.MatcherResult import io.kotest.matchers.collections.duplicates +import io.kotest.matchers.collections.shouldMatchEach +import io.kotest.matchers.neverNullMatcher +import io.kotest.matchers.should +import io.kotest.matchers.shouldHave +import io.kotest.matchers.shouldNot /* How should infinite sequences be detected, and how should they be dealt with? @@ -489,3 +495,7 @@ fun containAll(ts: List): Matcher> = object : Matcher Sequence.shouldMatchEach(vararg assertions: (T) -> Unit) = toList().shouldMatchEach(assertions.toList()) +infix fun Sequence.shouldMatchEach(assertions: List<(T) -> Unit>) = toList().shouldMatchEach(assertions) +fun Sequence.shouldMatchEach(expected: Sequence, asserter: (T, T) -> Unit) = toList().shouldMatchEach(expected.toList(), asserter) diff --git a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/collections/CollectionMatchersTest.kt b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/collections/CollectionMatchersTest.kt index 569db5f110f..81d9a05dff0 100644 --- a/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/collections/CollectionMatchersTest.kt +++ b/kotest-assertions/kotest-assertions-core/src/jvmTest/kotlin/com/sksamuel/kotest/matchers/collections/CollectionMatchersTest.kt @@ -881,6 +881,53 @@ class CollectionMatchersTest : WordSpec() { } } + "matchEach with actual / expected pairs" should { + "create proper matchers for collections of the same size" { + shouldThrow { + listOf(4, 3, 2, 1) should matchEach(listOf(1, 2, 3, 4)) { actual, expected -> + actual shouldBe expected + } + }.message shouldBe """ + Expected each element to pass its assertion, but found issues at indexes: [0, 1, 2, 3] + + 0 => expected:<1> but was:<4> + 1 => expected:<2> but was:<3> + 2 => expected:<3> but was:<2> + 3 => expected:<4> but was:<1> + """.trimIndent() + } + + "element missing on expected list" { + shouldThrow { + listOf(4, 3, 2, 1) should matchEach(listOf(1, 2, 3)) { actual, expected -> + actual shouldBe expected + } + }.message shouldBe """ + Expected each element to pass its assertion, but found issues at indexes: [0, 1, 2, 3] + + 0 => expected:<1> but was:<4> + 1 => expected:<2> but was:<3> + 2 => expected:<3> but was:<2> + 3 => Element has no corresponding assertion. Only 3 assertions provided + """.trimIndent() + } + + "element missing on actual list" { + shouldThrow { + listOf(4, 3, 2) should matchEach(listOf(1, 2, 3, 4)) { actual, expected -> + actual shouldBe expected + } + }.message shouldBe """ + Expected each element to pass its assertion, but found issues at indexes: [0, 1, 2, 3] + + 0 => expected:<1> but was:<4> + 1 => expected:<2> but was:<3> + 2 => expected:<3> but was:<2> + 3 => No actual element for assertion at index 3 + """.trimIndent() + } + } + "existInOrder" should { "test that a collection matches the predicates in the given order, duplicates permitted" { val col = listOf(1, 1, 2, 2, 3, 3)