Skip to content

Commit

Permalink
Merge pull request #316 from willowtreeapps/contains-exactly-in-any-o…
Browse files Browse the repository at this point in the history
…rder

Adds containsExactlyInAnyOrder to iterable and primitive / float arrays.
  • Loading branch information
Sean Amos committed Sep 1, 2020
2 parents 9c5897b + 31d69f6 commit d822528
Show file tree
Hide file tree
Showing 7 changed files with 382 additions and 5 deletions.
53 changes: 50 additions & 3 deletions assertk/src/commonMain/kotlin/assertk/assertions/iterable.kt
Expand Up @@ -25,7 +25,7 @@ fun Assert<Iterable<*>>.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<Iterable<*>>.containsNone(vararg elements: Any?) = given { actual ->
Expand All @@ -37,7 +37,7 @@ fun Assert<Iterable<*>>.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]
Expand All @@ -52,10 +52,17 @@ fun Assert<Iterable<*>>.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<Iterable<*>>.containsOnly(vararg elements: Any?) = given { actual ->
val notInActual = elements.filterNot { it in actual }
Expand All @@ -73,6 +80,46 @@ fun Assert<Iterable<*>>.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<Iterable<*>>.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())
}

internal 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.
*
Expand Down
7 changes: 7 additions & 0 deletions assertk/src/commonMain/kotlin/assertk/assertions/list.kt
Expand Up @@ -25,7 +25,14 @@ fun <T> Assert<List<T>>.index(index: Int): Assert<T> =
/**
* 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<List<*>>.containsExactly(vararg elements: Any?) = given { actual ->
val expected = elements.toList()
Expand Down
Expand Up @@ -74,6 +74,14 @@ class IterableTest {
assertThat(listOf(1, 2) as Iterable<Int>).containsOnly(2, 1)
}

@Test fun containsOnly_duplicate_elements_passes() {
assertThat(listOf(1, 2, 2) as Iterable<Int>).containsOnly(2, 1)
}

@Test fun containsOnly_duplicate_elements_passes2() {
assertThat(listOf(1, 2) as Iterable<Int>).containsOnly(2, 2, 1)
}

@Test fun containsOnly_more_elements_fails() {
val error = assertFails {
assertThat(listOf(1, 2, 3) as Iterable<Int>).containsOnly(2, 1)
Expand Down Expand Up @@ -111,6 +119,74 @@ class IterableTest {
}
//endregion

//region containsExactlyInAnyOrder
@Test fun containsExactlyInAnyOrder_only_elements_passes() {
assertThat(listOf(1, 2) as Iterable<Int>).containsExactlyInAnyOrder(2, 1)
}

@Test fun containsExactlyInAnyOrder_only_elements_passes2() {
assertThat(listOf(1, 2, 1) as Iterable<Int>).containsExactlyInAnyOrder(2, 1, 1)
}

@Test fun containsExactlyInAnyOrder_duplicate_elements_fails() {
val error = assertFails {
assertThat(listOf(1, 2, 2) as Iterable<Int>).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<Int>).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<Int>).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<Int>).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<Int>).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<Int>() as Iterable<Int>).each { it.isEqualTo(1) }
Expand Down
51 changes: 50 additions & 1 deletion assertk/src/template/assertk/assertions/floatArrayContains.kt
Expand Up @@ -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()
Expand All @@ -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")
Expand All @@ -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())
}
Expand Up @@ -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]
Expand All @@ -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")
Expand All @@ -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())
}

0 comments on commit d822528

Please sign in to comment.