Skip to content

Commit

Permalink
Add expectNoOtherElements, fix end detection with single section
Browse files Browse the repository at this point in the history
  • Loading branch information
DSteve595 committed Aug 6, 2024
1 parent 9d78529 commit 8af7caa
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 37 deletions.
96 changes: 59 additions & 37 deletions strikt-core/src/main/kotlin/strikt/assertions/Iterable.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package strikt.assertions

import strikt.api.Assertion
import strikt.api.Assertion.Builder
import strikt.internal.iterable.ElementWithOrderingConstraints
import strikt.internal.iterable.OrderingConstraint
Expand Down Expand Up @@ -531,11 +532,13 @@ fun <T : Iterable<E>, E : Comparable<E>> Builder<T>.isSorted() = isSorted(Compar
* // This can be useful if we expect our result to contain duplicates
* startNewSection()
*
* // Asserts that "g" is present and appears immediately after everything expected
* // in the last section ("a" through "f")
* expect("g").first()
* // Asserts that only the elements we'ev declared with `expect` are present, and nothing else.
* // This is optional.
* // If we don't call this, extra elements can still be present anywhere in the iterable
* // (or current section, if using `startNewSection`).
* expectNoOtherElements()
*
* // Asserts that all elements have been declared with `expect` and nothing else is present.
* // Asserts that nothing else is present after the elements we've declared with `expect`.
* // This is optional.
* // If we don't call this, extra elements can still be present after our last expected element.
* expectNoFurtherElements()
Expand Down Expand Up @@ -651,44 +654,53 @@ fun <T: Iterable<E>, E> Builder<T>.containsWithOrderingConstraints(
allSections.flatMap { it.elementsWithConstraints.map { it.element } }
) {
var elementsConsumed = 0
if (allSections.size == 1) {
val onlySection = allSections.single()
assertSectionConstraints(onlySection.elementsWithConstraints)
elementsConsumed = onlySection.elementsWithConstraints.size
} else {
allSections.forEach { section ->
val sectionConstraints = section.elementsWithConstraints
val sectionElementCount: Int
when (val endDefinedBy = section.endDefinedBy) {
SectionAssertionSpec.EndDefinition.DeclaredElementCount -> {
// Define the section by the number of elements declared with `expect`
sectionElementCount = sectionConstraints.size
get("section %s") { drop(elementsConsumed).take(sectionElementCount) }
.assert("has %s elements", sectionElementCount) {
if (it.size == sectionElementCount) {
pass()
} else {
fail(it.size, "only %s elements left in list")
}
fun consumeElementsAndAssertSection(section: SectionAssertionSpec<E>): Assertion.Builder<out Iterable<E>> {
val sectionElementCount: Int
val assertion = when (val endDefinedBy = section.endDefinedBy) {
SectionAssertionSpec.EndDefinition.DeclaredElementCount -> {
// Define the section by the number of elements declared with `expect`
sectionElementCount = section.elementsWithConstraints.size
get("section %s") { drop(elementsConsumed).take(sectionElementCount) }
.assert("has %s elements", sectionElementCount) {
if (it.size == sectionElementCount) {
pass()
} else {
fail(it.size, "only %s elements left in list")
}
.and { this.assertSectionConstraints(sectionConstraints) }
}
is SectionAssertionSpec.EndDefinition.DeclaredElement -> {
// Define the section by taking everything until the end element
val remainingElements = subject.drop(elementsConsumed)
val indexOfEndElement = remainingElements.indexOf(endDefinedBy.element)
sectionElementCount = if (indexOfEndElement == -1) 0 else indexOfEndElement + 1
assertThat("contains section ending with %s", endDefinedBy.element) {
indexOfEndElement != -1
}
.and {
get("section %s") { drop(elementsConsumed).take(sectionElementCount) }
.assertSectionConstraints(sectionConstraints)
}
}
is SectionAssertionSpec.EndDefinition.DeclaredElement -> {
// Define the section by taking everything until the end element
val remainingElements = subject.drop(elementsConsumed)
val indexOfEndElement = remainingElements.indexOf(endDefinedBy.element)
sectionElementCount = if (indexOfEndElement == -1) 0 else indexOfEndElement + 1
assertThat("contains section ending with %s", endDefinedBy.element) {
indexOfEndElement != -1
}
.get("section %s") { drop(elementsConsumed).take(sectionElementCount) }
}
elementsConsumed += sectionElementCount
}
.run {
val assertNoOtherElements = section.expectsNoOtherElements
if (assertNoOtherElements) {
assert("contains no other elements", expected = emptyList<E>()) {
if (elementsConsumed == it.count()) {
pass()
} else {
fail(actual = it.drop(elementsConsumed))
}
}
} else {
this
}
}
elementsConsumed += sectionElementCount
return assertion
}

allSections.forEach { section ->
consumeElementsAndAssertSection(section)
.and { this.assertSectionConstraints(section.elementsWithConstraints) }
}

val assertNoFurtherElements = builder.expectsNoFurtherElements ||
Expand Down Expand Up @@ -722,6 +734,16 @@ interface OrderingConstraintsAssertScope<E> {
/**
* Asserts that all elements have been declared with [expect] and nothing else is present.
*
* If this is not called, extra elements can still be present anywhere in the iterable
* (or current section, if using [startNewSection]).
*/
fun expectNoOtherElements()

/**
* Asserts that the end of the iterable has been declared
* (either by the amount of calls to [expect], or by asserting the last element with [last])
* and nothing else is present.
*
* If this is not called, extra elements can still be present after the last expected element.
*/
fun expectNoFurtherElements()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ internal class OrderingConstraintsAssertScopeImpl<E>: OrderingConstraintsAssertS
return constraintsBuilder
}

override fun expectNoOtherElements() {
currentBuildingSection.expectsNoOtherElements = true
}

override fun expectNoFurtherElements() {
expectsNoFurtherElements = true
}
Expand Down Expand Up @@ -88,6 +92,7 @@ internal data class ElementWithOrderingConstraints<E>(val element: E, val constr
internal class SectionAssertionSpec<E> {
val elementsWithConstraints = mutableListOf<ElementWithOrderingConstraints<E>>()
var endDefinedBy: EndDefinition<E> = EndDefinition.DeclaredElementCount
var expectsNoOtherElements = false

sealed class EndDefinition<out E> {
data object DeclaredElementCount : EndDefinition<Nothing>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,33 @@ class IterableOrderingConstraintsAssertions {
}
}

@Test
fun failsWithExtraElementAndExpectNoOtherElements() {
assertThrows<AssertionError> {
expectThat(listOf("a", "b", "c", "d"))
.containsWithOrderingConstraints {
expect("a")
expect("b")
expect("c")
expectNoOtherElements()
}
}
}

@Test
fun failsWithExtraElementInFirstSectionAndExpectNoOtherElements() {
assertThrows<AssertionError> {
expectThat(listOf("a", "b", "c", "d"))
.containsWithOrderingConstraints {
expect("a")
expect("c").last()
expectNoOtherElements()
startNewSection()
expect("d")
}
}
}

@Test
fun failsWithExtraElementAndExpectNoFurtherElements() {
assertThrows<AssertionError> {
Expand Down Expand Up @@ -338,6 +365,14 @@ class IterableOrderingConstraintsAssertions {
}
}

@Test
fun lastInPartiallyDeclaredList() {
expectThat(listOf("a", "b"))
.containsWithOrderingConstraints {
expect("b").last()
}
}

@Test
fun lastInPartiallyDeclaredSections() {
expectThat(listOf("a", "b", "c", "d"))
Expand Down

0 comments on commit 8af7caa

Please sign in to comment.