diff --git a/assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt b/assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt index 71690cd5..03d3772e 100644 --- a/assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt +++ b/assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt @@ -25,7 +25,7 @@ fun Assert>.doesNotContain(element: Any?) = given { actual -> } /** - * Asserts the collection does not contain any of the expected elements. + * Asserts the iterable does not contain any of the expected elements. * @see [containsAll] */ fun Assert>.containsNone(vararg elements: Any?) = given { actual -> @@ -37,7 +37,7 @@ fun Assert>.containsNone(vararg elements: Any?) = given { actual -> } /** - * Asserts the collection contains all the expected elements, in any order. The collection may also + * Asserts the iterable contains all the expected elements, in any order. The collection may also * contain additional elements. * @see [containsNone] * @see [containsExactly] @@ -52,10 +52,17 @@ fun Assert>.containsAll(vararg elements: Any?) = given { actual -> } /** - * Asserts the collection contains only the expected elements, in any order. + * Asserts the iterable contains only the expected elements, in any order. Duplicate values + * in the expected and actual are ignored. + * + * [1, 2] containsOnly [2, 1] passes + * [1, 2, 2] containsOnly [2, 1] passes + * [1, 2] containsOnly [2, 2, 1] passes + * * @see [containsNone] * @see [containsExactly] * @see [containsAll] + * @see [containsExactlyInAnyOrder] */ fun Assert>.containsOnly(vararg elements: Any?) = given { actual -> val notInActual = elements.filterNot { it in actual } @@ -73,6 +80,46 @@ fun Assert>.containsOnly(vararg elements: Any?) = given { actual -> }.toString()) } +/** + * Asserts the iterable contains exactly the expected elements, in any order. Each value in expected + * must correspond to a matching value in actual, and visa-versa. + * + * [1, 2] containsExactlyInAnyOrder [2, 1] passes + * [1, 2, 2] containsExactlyInAnyOrder [2, 1] fails + * [1, 2] containsExactlyInAnyOrder [2, 2, 1] fails + * + * @see [containsNone] + * @see [containsExactly] + * @see [containsAll] + * @see [containsOnly] + */ +fun Assert>.containsExactlyInAnyOrder(vararg elements: Any?) = given { actual -> + val notInActual = elements.toMutableList() + val notInExpected = actual.toMutableList() + elements.forEach { + if (notInExpected.contains(it)) { + notInExpected.removeFirst(it) + notInActual.removeFirst(it) + } + } + if (notInExpected.isEmpty() && notInActual.isEmpty()) { + return + } + expected(StringBuilder("to contain exactly in any order:${show(elements)} but was:${show(actual)}").apply { + if (notInActual.isNotEmpty()) { + append("\n elements not found:${show(notInActual)}") + } + if (notInExpected.isNotEmpty()) { + append("\n extra elements found:${show(notInExpected)}") + } + }.toString()) +} + +private fun MutableList<*>.removeFirst(value: Any?) { + val index = indexOf(value) + if (index > -1) removeAt(index) +} + /** * Asserts on each item in the iterable. The given lambda will be run for each item. * diff --git a/assertk/src/commonMain/kotlin/assertk/assertions/list.kt b/assertk/src/commonMain/kotlin/assertk/assertions/list.kt index ad667c9c..c77addee 100644 --- a/assertk/src/commonMain/kotlin/assertk/assertions/list.kt +++ b/assertk/src/commonMain/kotlin/assertk/assertions/list.kt @@ -25,7 +25,14 @@ fun Assert>.index(index: Int): Assert = /** * Asserts the list contains exactly the expected elements. They must be in the same order and * there must not be any extra elements. + * + * [1, 2] containsOnly [2, 1] fails + * [1, 2, 2] containsOnly [2, 1] fails + * [1, 2] containsOnly [2, 2, 1] fails + * * @see [containsAll] + * @see [containsOnly] + * @see [containsExactlyInAnyOrder] */ fun Assert>.containsExactly(vararg elements: Any?) = given { actual -> val expected = elements.toList() diff --git a/assertk/src/commonTest/kotlin/test/assertk/assertions/IterableTest.kt b/assertk/src/commonTest/kotlin/test/assertk/assertions/IterableTest.kt index 20d0cf49..8e5eecb2 100644 --- a/assertk/src/commonTest/kotlin/test/assertk/assertions/IterableTest.kt +++ b/assertk/src/commonTest/kotlin/test/assertk/assertions/IterableTest.kt @@ -74,6 +74,14 @@ class IterableTest { assertThat(listOf(1, 2) as Iterable).containsOnly(2, 1) } + @Test fun containsOnly_duplicate_elements_passes() { + assertThat(listOf(1, 2, 2) as Iterable).containsOnly(2, 1) + } + + @Test fun containsOnly_duplicate_elements_passes2() { + assertThat(listOf(1, 2) as Iterable).containsOnly(2, 2, 1) + } + @Test fun containsOnly_more_elements_fails() { val error = assertFails { assertThat(listOf(1, 2, 3) as Iterable).containsOnly(2, 1) @@ -111,6 +119,74 @@ class IterableTest { } //endregion + //region containsExactlyInAnyOrder + @Test fun containsExactlyInAnyOrder_only_elements_passes() { + assertThat(listOf(1, 2) as Iterable).containsExactlyInAnyOrder(2, 1) + } + + @Test fun containsExactlyInAnyOrder_only_elements_passes2() { + assertThat(listOf(1, 2, 1) as Iterable).containsExactlyInAnyOrder(2, 1, 1) + } + + @Test fun containsExactlyInAnyOrder_duplicate_elements_fails() { + val error = assertFails { + assertThat(listOf(1, 2, 2) as Iterable).containsExactlyInAnyOrder(2, 1) + } + assertEquals( + """expected to contain exactly in any order:<[2, 1]> but was:<[1, 2, 2]> + | extra elements found:<[2]> + """.trimMargin(), error.message + ) + } + + @Test fun containsExactlyInAnyOrder_duplicate_elements_fails2() { + val error = assertFails { + assertThat(listOf(1, 2) as Iterable).containsExactlyInAnyOrder(2, 2, 1) + } + assertEquals( + """expected to contain exactly in any order:<[2, 2, 1]> but was:<[1, 2]> + | elements not found:<[2]> + """.trimMargin(), error.message + ) + } + + @Test fun containsExactlyInAnyOrder_more_elements_fails() { + val error = assertFails { + assertThat(listOf(1, 2, 3) as Iterable).containsExactlyInAnyOrder(2, 1) + } + assertEquals( + """expected to contain exactly in any order:<[2, 1]> but was:<[1, 2, 3]> + | extra elements found:<[3]> + """.trimMargin(), error.message + ) + } + + @Test fun containsExactlyInAnyOrder_less_elements_fails() { + val error = assertFails { + assertThat(listOf(1, 2, 3) as Iterable).containsExactlyInAnyOrder(2, 1, 3, 4) + } + assertEquals( + """expected to contain exactly in any order:<[2, 1, 3, 4]> but was:<[1, 2, 3]> + | elements not found:<[4]> + """.trimMargin(), + error.message + ) + } + + @Test fun containsExactlyInAnyOrder_different_elements_fails() { + val error = assertFails { + assertThat(listOf(1) as Iterable).containsExactlyInAnyOrder(2) + } + assertEquals( + """expected to contain exactly in any order:<[2]> but was:<[1]> + | elements not found:<[2]> + | extra elements found:<[1]> + """.trimMargin(), + error.message + ) + } + //endregion + //region each @Test fun each_empty_list_passes() { assertThat(emptyList() as Iterable).each { it.isEqualTo(1) } diff --git a/assertk/src/template/assertk/assertions/floatArrayContains.kt b/assertk/src/template/assertk/assertions/floatArrayContains.kt index e4548386..5131c177 100644 --- a/assertk/src/template/assertk/assertions/floatArrayContains.kt +++ b/assertk/src/template/assertk/assertions/floatArrayContains.kt @@ -59,10 +59,17 @@ fun Assert<$T>.containsAll(vararg elements: $E) = given { actual -> } /** - * Asserts the $T contains only the expected elements, in any order. + * Asserts the $T contains only the expected elements, in any order. Duplicate values + * in the expected and actual are ignored. + * + * [1, 2] containsOnly [2, 1] passes + * [1, 2, 2] containsOnly [2, 1] passes + * [1, 2] containsOnly [2, 2, 1] passes + * * @see [containsNone] * @see [containsExactly] * @see [containsAll] + * @see [containsExactlyInAnyOrder] */ fun Assert<$T>.containsOnly(vararg elements: $E) = given { actual -> val actualList = actual.asList() @@ -85,6 +92,11 @@ fun Assert<$T>.containsOnly(vararg elements: $E) = given { actual -> /** * Asserts the $T contains exactly the expected elements. They must be in the same order and * there must not be any extra elements. + * + * [1, 2] containsOnly [2, 1] fails + * [1, 2, 2] containsOnly [2, 1] fails + * [1, 2] containsOnly [2, 2, 1] fails + * * @see [containsAll] */ @JvmName("$NContainsExactly") @@ -96,3 +108,40 @@ fun Assert<$T>.containsExactly(vararg elements: $E) = given { actual -> expected(listDifferExpected(elementsList, actualList)) } +/** + * Asserts the $T contains exactly the expected elements, in any order. Each value in expected + * must correspond to a matching value in actual, and visa-versa. + * + * [1, 2] containsExactlyInAnyOrder [2, 1] passes + * [1, 2, 2] containsExactlyInAnyOrder [2, 1] fails + * [1, 2] containsExactlyInAnyOrder [2, 2, 1] fails + * + * @see [containsNone] + * @see [containsExactly] + * @see [containsAll] + * @see [containsOnly] + */ +@JvmName("$NContainsExactlyInAnyOrder") +fun Assert<$T>.containsExactlyInAnyOrder(vararg elements: $E) = given { actual -> + val actualList = actual.asList() + val elementsList = elements.asList() + val notInActual = elementsList.toMutableList() + val notInExpected = actualList.toMutableList() + elements.forEach { + if (notInExpected.contains(it)) { + notInExpected.removeFirst(it) + notInActual.removeFirst(it) + } + } + if (notInExpected.isEmpty() && notInActual.isEmpty()) { + return + } + expected(StringBuilder("to contain exactly in any order:${show(elements)} but was:${show(actual)}").apply { + if (notInActual.isNotEmpty()) { + append("\n elements not found:${show(notInActual)}") + } + if (notInExpected.isNotEmpty()) { + append("\n extra elements found:${show(notInExpected)}") + } + }.toString()) +} \ No newline at end of file diff --git a/assertk/src/template/assertk/assertions/primativeArrayContains.kt b/assertk/src/template/assertk/assertions/primativeArrayContains.kt index d2a18e80..75520642 100644 --- a/assertk/src/template/assertk/assertions/primativeArrayContains.kt +++ b/assertk/src/template/assertk/assertions/primativeArrayContains.kt @@ -56,7 +56,13 @@ fun Assert<$T>.containsAll(vararg elements: $E) = given { actual -> } /** - * Asserts the $T contains only the expected elements, in any order. + * Asserts the $T contains only the expected elements, in any order. Duplicate values + * in the expected and actual are ignored. + * + * [1, 2] containsOnly [2, 1] passes + * [1, 2, 2] containsOnly [2, 1] passes + * [1, 2] containsOnly [2, 2, 1] passes + * * @see [containsNone] * @see [containsExactly] * @see [containsAll] @@ -80,6 +86,11 @@ fun Assert<$T>.containsOnly(vararg elements: $E) = given { actual -> /** * Asserts the $T contains exactly the expected elements. They must be in the same order and * there must not be any extra elements. + * + * [1, 2] containsOnly [2, 1] fails + * [1, 2, 2] containsOnly [2, 1] fails + * [1, 2] containsOnly [2, 2, 1] fails + * * @see [containsAll] */ @JvmName("$NContainsExactly") @@ -89,3 +100,38 @@ fun Assert<$T>.containsExactly(vararg elements: $E) = given { actual -> expected(listDifferExpected(elements.toList(), actual.toList())) } +/** + * Asserts the $T contains exactly the expected elements, in any order. Each value in expected + * must correspond to a matching value in actual, and visa-versa. + * + * [1, 2] containsExactlyInAnyOrder [2, 1] passes + * [1, 2, 2] containsExactlyInAnyOrder [2, 1] fails + * [1, 2] containsExactlyInAnyOrder [2, 2, 1] fails + * + * @see [containsNone] + * @see [containsExactly] + * @see [containsAll] + * @see [containsOnly] + */ +@JvmName("$NContainsExactlyInAnyOrder") +fun Assert<$T>.containsExactlyInAnyOrder(vararg elements: $E) = given { actual -> + val notInActual = elements.toMutableList() + val notInExpected = actual.toMutableList() + elements.forEach { + if (notInExpected.contains(it)) { + notInExpected.removeFirst(it) + notInActual.removeFirst(it) + } + } + if (notInExpected.isEmpty() && notInActual.isEmpty()) { + return + } + expected(StringBuilder("to contain exactly in any order:${show(elements)} but was:${show(actual)}").apply { + if (notInActual.isNotEmpty()) { + append("\n elements not found:${show(notInActual)}") + } + if (notInExpected.isNotEmpty()) { + append("\n extra elements found:${show(notInExpected)}") + } + }.toString()) +} \ No newline at end of file diff --git a/assertk/src/testTemplate/test/assertk/assertions/FloatArrayContainsTest.kt b/assertk/src/testTemplate/test/assertk/assertions/FloatArrayContainsTest.kt index bf0bd19f..e49aa570 100644 --- a/assertk/src/testTemplate/test/assertk/assertions/FloatArrayContainsTest.kt +++ b/assertk/src/testTemplate/test/assertk/assertions/FloatArrayContainsTest.kt @@ -87,6 +87,14 @@ class $TContainsTest { assertThat($NOf(1.to$E(), 2.to$E())).containsOnly(2.to$E(), 1.to$E()) } + @Test fun containsOnly_duplicate_elements_passes() { + assertThat($NOf(1.to$E(), 2.to$E(), 2.to$E())).containsOnly(2.to$E(), 1.to$E()) + } + + @Test fun containsOnly_duplicate_elements_passes2() { + assertThat($NOf(1.to$E(), 2.to$E())).containsOnly(2.to$E(), 2.to$E(), 1.to$E()) + } + @Test fun containsOnly_more_elements_fails() { val error = assertFails { assertThat($NOf(1.to$E(), 2.to$E(), 3.to$E())).containsOnly(2.to$E(), 1.to$E()) @@ -202,6 +210,74 @@ class $TContainsTest { } //endregion + //region containsExactlyInAnyOrder + @Test fun containsExactlyInAnyOrder_only_elements_passes() { + assertThat($NOf(1.to$E(), 2.to$E())).containsExactlyInAnyOrder(2.to$E(), 1.to$E()) + } + + @Test fun containsExactlyInAnyOrder_only_elements_passes2() { + assertThat($NOf(1.to$E(), 2.to$E(), 1.to$E())).containsExactlyInAnyOrder(2.to$E(), 1.to$E(), 1.to$E()) + } + + @Test fun containsExactlyInAnyOrder_duplicate_elements_fails() { + val error = assertFails { + assertThat($NOf(1.to$E(), 2.to$E(), 2.to$E())).containsExactlyInAnyOrder(2.to$E(), 1.to$E()) + } + assertEquals( + """expected to contain exactly in any order:<${show(listOf(2.to$E(), 1.to$E()), "")}> but was:<${show(listOf(1.to$E(), 2.to$E(), 2.to$E()), "")}> + | extra elements found:<[${show(2.to$E(), "")}]> + """.trimMargin(), error.message + ) + } + + @Test fun containsExactlyInAnyOrder_duplicate_elements_fails2() { + val error = assertFails { + assertThat($NOf(1.to$E(), 2.to$E())).containsExactlyInAnyOrder(2.to$E(), 2.to$E(), 1.to$E()) + } + assertEquals( + """expected to contain exactly in any order:<${show(listOf(2.to$E(), 2.to$E(), 1.to$E()), "")}> but was:<${show(listOf(1.to$E(), 2.to$E()), "")}> + | elements not found:<[${show(2.to$E(), "")}]> + """.trimMargin(), error.message + ) + } + + @Test fun containsExactlyInAnyOrder_more_elements_fails() { + val error = assertFails { + assertThat($NOf(1.to$E(), 2.to$E(), 3.to$E())).containsExactlyInAnyOrder(2.to$E(), 1.to$E()) + } + assertEquals( + """expected to contain exactly in any order:<${show(listOf(2.to$E(), 1.to$E()), "")}> but was:<${show(listOf(1.to$E(), 2.to$E(), 3.to$E()), "")}> + | extra elements found:<[${show(3.to$E(), "")}]> + """.trimMargin(), error.message + ) + } + + @Test fun containsExactlyInAnyOrder_less_elements_fails() { + val error = assertFails { + assertThat($NOf(1.to$E(), 2.to$E(), 3.to$E())).containsExactlyInAnyOrder(2.to$E(), 1.to$E(), 3.to$E(), 4.to$E()) + } + assertEquals( + """expected to contain exactly in any order:<${show(listOf(2.to$E(), 1.to$E(), 3.to$E(), 4.to$E()), "")}> but was:<${show(listOf(1.to$E(), 2.to$E(), 3.to$E()), "")}> + | elements not found:<[${show(4.to$E(), "")}]> + """.trimMargin(), + error.message + ) + } + + @Test fun containsExactlyInAnyOrder_different_elements_fails() { + val error = assertFails { + assertThat($NOf(1.to$E())).containsExactlyInAnyOrder(2.to$E()) + } + assertEquals( + """expected to contain exactly in any order:<[${show(2.to$E(), "")}]> but was:<[${show(1.to$E(), "")}]> + | elements not found:<[${show(2.to$E(), "")}]> + | extra elements found:<[${show(1.to$E(), "")}]> + """.trimMargin(), + error.message + ) + } + //endregion + //region each @Test fun each_empty_list_passes() { assertThat($NOf()).each { it.isEqualTo(1) } diff --git a/assertk/src/testTemplate/test/assertk/assertions/PrimativeArrayContainsTest.kt b/assertk/src/testTemplate/test/assertk/assertions/PrimativeArrayContainsTest.kt index 6b8fa4e3..d868d365 100644 --- a/assertk/src/testTemplate/test/assertk/assertions/PrimativeArrayContainsTest.kt +++ b/assertk/src/testTemplate/test/assertk/assertions/PrimativeArrayContainsTest.kt @@ -76,6 +76,14 @@ class $TContainsTest { assertThat($NOf(1.to$E(), 2.to$E())).containsOnly(2.to$E(), 1.to$E()) } + @Test fun containsOnly_duplicate_elements_passes() { + assertThat($NOf(1.to$E(), 2.to$E(), 2.to$E())).containsOnly(2.to$E(), 1.to$E()) + } + + @Test fun containsOnly_duplicate_elements_passes2() { + assertThat($NOf(1.to$E(), 2.to$E())).containsOnly(2.to$E(), 2.to$E(), 1.to$E()) + } + @Test fun containsOnly_more_elements_fails() { val error = assertFails { assertThat($NOf(1.to$E(), 2.to$E(), 3.to$E())).containsOnly(2.to$E(), 1.to$E()) @@ -191,6 +199,74 @@ class $TContainsTest { } //endregion + //region containsExactlyInAnyOrder + @Test fun containsExactlyInAnyOrder_only_elements_passes() { + assertThat($NOf(1.to$E(), 2.to$E())).containsExactlyInAnyOrder(2.to$E(), 1.to$E()) + } + + @Test fun containsExactlyInAnyOrder_only_elements_passes2() { + assertThat($NOf(1.to$E(), 2.to$E(), 1.to$E())).containsExactlyInAnyOrder(2.to$E(), 1.to$E(), 1.to$E()) + } + + @Test fun containsExactlyInAnyOrder_duplicate_elements_fails() { + val error = assertFails { + assertThat($NOf(1.to$E(), 2.to$E(), 2.to$E())).containsExactlyInAnyOrder(2.to$E(), 1.to$E()) + } + assertEquals( + """expected to contain exactly in any order:<${show(listOf(2.to$E(), 1.to$E()), "")}> but was:<${show(listOf(1.to$E(), 2.to$E(), 2.to$E()), "")}> + | extra elements found:<[${show(2.to$E(), "")}]> + """.trimMargin(), error.message + ) + } + + @Test fun containsExactlyInAnyOrder_duplicate_elements_fails2() { + val error = assertFails { + assertThat($NOf(1.to$E(), 2.to$E())).containsExactlyInAnyOrder(2.to$E(), 2.to$E(), 1.to$E()) + } + assertEquals( + """expected to contain exactly in any order:<${show(listOf(2.to$E(), 2.to$E(), 1.to$E()), "")}> but was:<${show(listOf(1.to$E(), 2.to$E()), "")}> + | elements not found:<[${show(2.to$E(), "")}]> + """.trimMargin(), error.message + ) + } + + @Test fun containsExactlyInAnyOrder_more_elements_fails() { + val error = assertFails { + assertThat($NOf(1.to$E(), 2.to$E(), 3.to$E())).containsExactlyInAnyOrder(2.to$E(), 1.to$E()) + } + assertEquals( + """expected to contain exactly in any order:<${show(listOf(2.to$E(), 1.to$E()), "")}> but was:<${show(listOf(1.to$E(), 2.to$E(), 3.to$E()), "")}> + | extra elements found:<[${show(3.to$E(), "")}]> + """.trimMargin(), error.message + ) + } + + @Test fun containsExactlyInAnyOrder_less_elements_fails() { + val error = assertFails { + assertThat($NOf(1.to$E(), 2.to$E(), 3.to$E())).containsExactlyInAnyOrder(2.to$E(), 1.to$E(), 3.to$E(), 4.to$E()) + } + assertEquals( + """expected to contain exactly in any order:<${show(listOf(2.to$E(), 1.to$E(), 3.to$E(), 4.to$E()), "")}> but was:<${show(listOf(1.to$E(), 2.to$E(), 3.to$E()), "")}> + | elements not found:<[${show(4.to$E(), "")}]> + """.trimMargin(), + error.message + ) + } + + @Test fun containsExactlyInAnyOrder_different_elements_fails() { + val error = assertFails { + assertThat($NOf(1.to$E())).containsExactlyInAnyOrder(2.to$E()) + } + assertEquals( + """expected to contain exactly in any order:<[${show(2.to$E(), "")}]> but was:<[${show(1.to$E(), "")}]> + | elements not found:<[${show(2.to$E(), "")}]> + | extra elements found:<[${show(1.to$E(), "")}]> + """.trimMargin(), + error.message + ) + } + //endregion + //region each @Test fun each_empty_list_passes() { assertThat($NOf()).each { it.isEqualTo(1) }