From 20a38b5801ba30dac33249d3665c775d782f821d Mon Sep 17 00:00:00 2001 From: Robert Stoll Date: Tue, 3 Mar 2020 21:50:47 +0100 Subject: [PATCH] add throwable and charSequenceAssertions to the new infix API moreover: - add ambiguityTest to CharSequenceSpecBase - introduce toVarArg which incorporates the hasNext check - make feature KProperty contra-variant -> otherwise something like Throwable::message does not work - unfortunately same for Feature and FeatureWithCreator but due to a Kotlin bug (would not be necessary - also the same for the param object Values (also a Kotlin bug) --- .../fluent/en_GB/charSequenceAssertions.kt | 4 + .../en_GB/charSequenceContainsCreators.kt | 42 +- .../iterableContainsInAnyOrderCreators.kt | 5 +- .../iterableContainsInAnyOrderOnlyCreators.kt | 5 +- .../iterableContainsInOrderOnlyCreators.kt | 5 +- .../en_GB/CharSequenceAssertionsSpec.kt | 27 +- ...arSequenceContainsAtLeastAssertionsSpec.kt | 2 +- ...quenceContainsContainsNotAssertionsSpec.kt | 25 +- .../en_GB/CharSequenceContainsSpecBase.kt | 28 ++ .../api/infix/en_GB/charSequenceAssertions.kt | 324 +++++++++++++++ .../en_GB/charSequenceContainsCheckers.kt | 90 +++++ .../en_GB/charSequenceContainsCreators.kt | 376 ++++++++++++++++++ .../charSequenceContainsSearchBehaviours.kt | 33 ++ .../contains.builders/AtLeastCheckerOption.kt | 14 + .../contains.builders/AtMostCheckerOption.kt | 13 + .../ButAtMostCheckerOption.kt | 13 + .../contains.builders/ExactlyCheckerOption.kt | 13 + .../contains.builders/NotCheckerOption.kt | 13 + .../NotOrAtMostCheckerOption.kt | 13 + .../impl/AtLeastCheckerOptionImpl.kt | 29 ++ .../impl/AtMostCheckerOptionImpl.kt | 33 ++ .../impl/ButAtMostCheckerOptionImpl.kt | 39 ++ .../impl/ExactlyCheckerOptionImpl.kt | 29 ++ .../impl/NotCheckerOptionImpl.kt | 21 + .../impl/NotOrAtMostCheckerOptionImpl.kt | 29 ++ .../impl/nameContainsNotFun.kt | 11 + .../api/infix/en_GB/featureAssertions.kt | 14 +- .../atrium/api/infix/en_GB/keywords.kt | 21 + .../api/infix/en_GB/parameterObjects.kt | 25 ++ .../api/infix/en_GB/throwableAssertions.kt | 50 +++ .../infix/en_GB/CharSequenceAssertionsSpec.kt | 50 +++ ...arSequenceContainsAtLeastAssertionsSpec.kt | 174 ++++++++ ...harSequenceContainsAtMostAssertionsSpec.kt | 42 ++ ...quenceContainsContainsNotAssertionsSpec.kt | 31 ++ ...arSequenceContainsExactlyAssertionsSpec.kt | 40 ++ .../CharSequenceContainsNotAssertionsSpec.kt | 29 ++ ...quenceContainsNotOrAtMostAssertionsSpec.kt | 42 ++ ...CharSequenceContainsRegexAssertionsSpec.kt | 139 +++++++ .../en_GB/CharSequenceContainsSpecBase.kt | 60 +++ .../infix/en_GB/ThrowableAssertionsSpec.kt | 29 ++ .../domain/builders/utils/VarArgHelper.kt | 11 + ...CharSequenceContainsRegexAssertionsSpec.kt | 20 +- 42 files changed, 1955 insertions(+), 58 deletions(-) create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceAssertions.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceContainsCheckers.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceContainsCreators.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceContainsSearchBehaviours.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/AtLeastCheckerOption.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/AtMostCheckerOption.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/ButAtMostCheckerOption.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/ExactlyCheckerOption.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/NotCheckerOption.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/NotOrAtMostCheckerOption.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/AtLeastCheckerOptionImpl.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/AtMostCheckerOptionImpl.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/ButAtMostCheckerOptionImpl.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/ExactlyCheckerOptionImpl.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/NotCheckerOptionImpl.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/NotOrAtMostCheckerOptionImpl.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/nameContainsNotFun.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/throwableAssertions.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceAssertionsSpec.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsAtLeastAssertionsSpec.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsAtMostAssertionsSpec.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsContainsNotAssertionsSpec.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsExactlyAssertionsSpec.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsNotAssertionsSpec.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsNotOrAtMostAssertionsSpec.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsRegexAssertionsSpec.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsSpecBase.kt create mode 100644 apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/ThrowableAssertionsSpec.kt diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/charSequenceAssertions.kt b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/charSequenceAssertions.kt index 49ca8b16c..ca4754012 100644 --- a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/charSequenceAssertions.kt +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/charSequenceAssertions.kt @@ -229,6 +229,8 @@ fun Expect.isNotBlank() = addAssertion(ExpectImpl.charSequ /** * Expects that the subject of the assertion (a [CharSequence]) matches the given [expected] [Regex]. * + * In contrast to [containsRegex] it does not look for a partial match but for an entire match. + * * @return An [Expect] for the current subject of the assertion. * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. * @@ -240,6 +242,8 @@ fun Expect.matches(expected: Regex) = /** * Expects that the subject of the assertion (a [CharSequence]) mismatches the given [expected] [Regex]. * + * In contrast to `containsNot.regex` it does not look for a partial match but for an entire match. + * * @return An [Expect] for the current subject of the assertion. * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. * diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/charSequenceContainsCreators.kt b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/charSequenceContainsCreators.kt index 639b35b56..59814f748 100644 --- a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/charSequenceContainsCreators.kt +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/charSequenceContainsCreators.kt @@ -3,7 +3,8 @@ package ch.tutteli.atrium.api.fluent.en_GB import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.domain.builders.ExpectImpl import ch.tutteli.atrium.domain.builders.creating.basic.contains.addAssertion -import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains +import ch.tutteli.atrium.domain.builders.utils.toVarArg +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains.* import ch.tutteli.atrium.domain.creating.charsequence.contains.searchbehaviours.IgnoringCaseSearchBehaviour import ch.tutteli.atrium.domain.creating.charsequence.contains.searchbehaviours.NoOpSearchBehaviour import ch.tutteli.kbox.glue @@ -26,7 +27,7 @@ import kotlin.jvm.JvmName * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. * @throws IllegalArgumentException in case [expected] is not a [CharSequence], [Number] or [Char]. */ -fun CharSequenceContains.CheckerOption.value(expected: Any): Expect = +fun CheckerOption.value(expected: Any): Expect = values(expected) /** @@ -55,7 +56,7 @@ fun CharSequenceContains.CheckerOption CharSequenceContains.CheckerOption.values( +fun CheckerOption.values( expected: Any, vararg otherExpected: Any ): Expect = addAssertion(ExpectImpl.charSequence.contains.values(this, expected glue otherExpected)) @@ -79,7 +80,7 @@ fun CharSequenceContains.CheckerOption CharSequenceContains.CheckerOption.value( +fun CheckerOption.value( expected: Any ): Expect = values(expected) @@ -110,7 +111,7 @@ fun CharSequenceContains.CheckerOption CharSequenceContains.CheckerOption.values( +fun CheckerOption.values( expected: Any, vararg otherExpected: Any ): Expect = addAssertion(ExpectImpl.charSequence.contains.valuesIgnoringCase(this, expected glue otherExpected)) @@ -120,7 +121,7 @@ fun CharSequenceContains.CheckerOption CharSequenceContains.CheckerOption CharSequenceContains.Builder.value(expected: Any): Expect = +fun Builder.value(expected: Any): Expect = atLeast(1).value(expected) /** @@ -159,7 +160,7 @@ fun CharSequenceContains.Builder CharSequenceContains.Builder.values( +fun Builder.values( expected: Any, vararg otherExpected: Any ): Expect = atLeast(1).values(expected, *otherExpected) @@ -185,13 +186,13 @@ fun CharSequenceContains.Builder CharSequenceContains.CheckerOption.regex( +fun CheckerOption.regex( pattern: String, vararg otherPatterns: String ): Expect = addAssertion(ExpectImpl.charSequence.contains.regex(this, pattern glue otherPatterns)) /** - * Finishes the specification of the sophisticated `contains` assertion where the given regular expression [pattern] + * Finishes the specification of the sophisticated `contains` assertion where the given [Regex] [pattern] * as well as the [otherPatterns] are expected to have a match, using a non disjoint search. * * By non disjoint is meant that `'aa'` in `'aaaa'` is found three times and not only two times. @@ -213,7 +214,8 @@ fun CharSequenceContains.CheckerOption CharSequenceContains.CheckerOption.regex( +//TODO rename to `matchFor` with 1.0.0 +fun CheckerOption.regex( pattern: Regex, vararg otherPatterns: Regex ): Expect = addAssertion(ExpectImpl.charSequence.contains.regex(this, pattern glue otherPatterns)) @@ -240,7 +242,7 @@ fun CharSequenceContains.CheckerOption CharSequenceContains.CheckerOption.regex( +fun CheckerOption.regex( pattern: String, vararg otherPatterns: String ): Expect = addAssertion(ExpectImpl.charSequence.contains.regexIgnoringCase(this, pattern glue otherPatterns)) @@ -269,7 +271,7 @@ fun CharSequenceContains.CheckerOption CharSequenceContains.Builder.regex( +fun Builder.regex( pattern: String, vararg otherPatterns: String ): Expect = atLeast(1).regex(pattern, *otherPatterns) @@ -292,13 +294,14 @@ fun CharSequenceContains.Builder CharSequenceContains.CheckerOption.elementsOf( +fun CheckerOption.elementsOf( expectedIterable: Iterable ): Expect { - require(expectedIterable.iterator().hasNext()) { "Iterable without elements are not allowed." } - return values(expectedIterable.first(), *expectedIterable.drop(1).toTypedArray()) + val (first, rest) = toVarArg(expectedIterable) + return values(first, *rest) } /** @@ -319,12 +322,13 @@ fun CharSequenceContains.CheckerOption CharSequenceContains.CheckerOption.elementsOf( +fun CheckerOption.elementsOf( expectedIterable: Iterable ): Expect { - require(expectedIterable.iterator().hasNext()) { "Iterable without elements are not allowed." } - return values(expectedIterable.first(), *expectedIterable.drop(1).toTypedArray()) + val (first, rest) = toVarArg(expectedIterable) + return values(first, *rest) } diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/iterableContainsInAnyOrderCreators.kt b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/iterableContainsInAnyOrderCreators.kt index 4411dd470..4169c4434 100644 --- a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/iterableContainsInAnyOrderCreators.kt +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/iterableContainsInAnyOrderCreators.kt @@ -3,6 +3,7 @@ package ch.tutteli.atrium.api.fluent.en_GB import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.domain.builders.ExpectImpl import ch.tutteli.atrium.domain.builders.creating.basic.contains.addAssertion +import ch.tutteli.atrium.domain.builders.utils.toVarArg import ch.tutteli.atrium.domain.creating.iterable.contains.IterableContains import ch.tutteli.atrium.domain.creating.iterable.contains.searchbehaviours.InAnyOrderSearchBehaviour import ch.tutteli.kbox.glue @@ -107,6 +108,6 @@ fun > IterableContains.CheckerOption> IterableContains.CheckerOption.elementsOf( expectedIterable: Iterable ): Expect { - require(expectedIterable.iterator().hasNext()) { "Iterable without elements are not allowed." } - return values(expectedIterable.first(), *expectedIterable.drop(1).toTypedArray()) + val (first, rest) = toVarArg(expectedIterable) + return values(first, *rest) } diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/iterableContainsInAnyOrderOnlyCreators.kt b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/iterableContainsInAnyOrderOnlyCreators.kt index dd2e7d7da..3efb58d6a 100644 --- a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/iterableContainsInAnyOrderOnlyCreators.kt +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/iterableContainsInAnyOrderOnlyCreators.kt @@ -3,6 +3,7 @@ package ch.tutteli.atrium.api.fluent.en_GB import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.domain.builders.ExpectImpl import ch.tutteli.atrium.domain.builders.creating.basic.contains.addAssertion +import ch.tutteli.atrium.domain.builders.utils.toVarArg import ch.tutteli.atrium.domain.creating.iterable.contains.IterableContains import ch.tutteli.atrium.domain.creating.iterable.contains.searchbehaviours.InAnyOrderOnlySearchBehaviour import ch.tutteli.kbox.glue @@ -128,6 +129,6 @@ fun > IterableContains.Builder> IterableContains.Builder.elementsOf( expectedIterable: Iterable ): Expect { - require(expectedIterable.iterator().hasNext()) { "Iterable without elements are not allowed" } - return values(expectedIterable.first(), *expectedIterable.drop(1).toTypedArray()) + val (first, rest) = toVarArg(expectedIterable) + return values(first, *rest) } diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/iterableContainsInOrderOnlyCreators.kt b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/iterableContainsInOrderOnlyCreators.kt index 55d400301..dda601b0d 100644 --- a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/iterableContainsInOrderOnlyCreators.kt +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/fluent/en_GB/iterableContainsInOrderOnlyCreators.kt @@ -3,6 +3,7 @@ package ch.tutteli.atrium.api.fluent.en_GB import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.domain.builders.ExpectImpl import ch.tutteli.atrium.domain.builders.creating.basic.contains.addAssertion +import ch.tutteli.atrium.domain.builders.utils.toVarArg import ch.tutteli.atrium.domain.creating.iterable.contains.IterableContains import ch.tutteli.atrium.domain.creating.iterable.contains.searchbehaviours.InOrderOnlySearchBehaviour import ch.tutteli.kbox.glue @@ -118,6 +119,6 @@ fun > IterableContains.Builder> IterableContains.Builder.elementsOf( expectedIterable: Iterable ): Expect { - require(expectedIterable.iterator().hasNext()) { "Iterable without elements are not allowed." } - return values(expectedIterable.first(), *expectedIterable.drop(1).toTypedArray()) + val (first, rest) = toVarArg(expectedIterable) + return values(first, *rest) } diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceAssertionsSpec.kt b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceAssertionsSpec.kt index f2615bb64..9ae85d4af 100644 --- a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceAssertionsSpec.kt +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceAssertionsSpec.kt @@ -3,8 +3,9 @@ package ch.tutteli.atrium.api.fluent.en_GB import ch.tutteli.atrium.creating.Expect import ch.tutteli.atrium.specs.fun0 import ch.tutteli.atrium.specs.fun1 +import ch.tutteli.atrium.specs.notImplemented -object CharSequenceAssertionsSpec : ch.tutteli.atrium.specs.integration.CharSequenceAssertionsSpec( +class CharSequenceAssertionsSpec : ch.tutteli.atrium.specs.integration.CharSequenceAssertionsSpec( fun0(Expect::isEmpty), fun0(Expect::isNotEmpty), fun0(Expect::isNotBlank), @@ -18,4 +19,26 @@ object CharSequenceAssertionsSpec : ch.tutteli.atrium.specs.integration.CharSequ fun1(Expect::endsNotWith), fun1(Expect::matches), fun1(Expect::mismatches) -) +) { + @Suppress("unused", "UNUSED_VALUE") + private fun ambiguityTest() { + val a1: Expect = notImplemented() + + a1.isEmpty() + a1.isNotEmpty() + a1.isNotBlank() + + a1.startsWith("expected") + a1.startsNotWith("expected") + a1.endsWith("expected") + a1.endsNotWith("expected") + + a1.startsWith('a') + a1.startsNotWith('a') + a1.endsWith('a') + a1.endsNotWith('a') + + a1.matches(Regex("a")) + a1.mismatches(Regex("a")) + } +} diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceContainsAtLeastAssertionsSpec.kt b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceContainsAtLeastAssertionsSpec.kt index 8c40b75b1..6de3cb375 100644 --- a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceContainsAtLeastAssertionsSpec.kt +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceContainsAtLeastAssertionsSpec.kt @@ -155,7 +155,7 @@ class CharSequenceContainsAtLeastAssertionsSpec : Spek({ aX: Array ) = expect.contains.ignoringCase.atLeast(atLeast).butAtMost(butAtMost).elementsOf(listOf(a, *aX)) - private fun getContainsNotPair() = containsNot to Companion::getErrorMsgContainsNot + private fun getContainsNotPair() = containsNot to ::getErrorMsgContainsNot private fun getErrorMsgContainsNot(times: Int) = "use $containsNot instead of $atLeast($times)" diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceContainsContainsNotAssertionsSpec.kt b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceContainsContainsNotAssertionsSpec.kt index a6708ee78..8ea1a64ec 100644 --- a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceContainsContainsNotAssertionsSpec.kt +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceContainsContainsNotAssertionsSpec.kt @@ -1,28 +1,21 @@ package ch.tutteli.atrium.api.fluent.en_GB import ch.tutteli.atrium.creating.Expect -import kotlin.reflect.KFunction3 +import ch.tutteli.atrium.specs.fun2 +import ch.tutteli.atrium.specs.notImplemented class CharSequenceContainsContainsNotAssertionsSpec : ch.tutteli.atrium.specs.integration.CharSequenceContainsContainsNotAssertionsSpec( - getContainsPair(), - getContainsNotPair(), + fun2>(Expect::contains), + fun2>(Expect::containsNot), "◆ ", "⚬ ", "▶ " ) { - companion object : CharSequenceContainsSpecBase() { - private val containsFun: KFunction3, Any, Array, Expect> = - Expect::contains - fun getContainsPair() = containsFun.name to Companion::containsShortcut + @Suppress("unused", "UNUSED_VALUE") + private fun ambiguityTest() { + val a1: Expect = notImplemented() - private fun containsShortcut(expect: Expect, a: Any, aX: Array) = expect.contains(a, *aX) - - private val containsNotFun: KFunction3, Any, Array, Expect> = - Expect::containsNot - - private fun getContainsNotPair() = containsNotFun.name to Companion::containsNotShortcut - - private fun containsNotShortcut(expect: Expect, a: Any, aX: Array) = - expect.containsNot(a, *aX) + a1.contains(1, "a", 'c') + a1.containsNot(1, "a", 'c') } } diff --git a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceContainsSpecBase.kt b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceContainsSpecBase.kt index 0298ff043..f7c2f6886 100644 --- a/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceContainsSpecBase.kt +++ b/apis/fluent-en_GB/atrium-api-fluent-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/fluent/en_GB/CharSequenceContainsSpecBase.kt @@ -6,6 +6,7 @@ import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceConta import ch.tutteli.atrium.domain.creating.charsequence.contains.searchbehaviours.NoOpSearchBehaviour import ch.tutteli.atrium.specs.fun2 import ch.tutteli.atrium.specs.name +import ch.tutteli.atrium.specs.notImplemented import kotlin.reflect.KFunction3 import kotlin.reflect.KProperty @@ -28,4 +29,31 @@ abstract class CharSequenceContainsSpecBase { > = CharSequenceContains.CheckerOption<*, NoOpSearchBehaviour>::regex protected val regex = regexKFun.name protected val ignoringCase = CharSequenceContains.Builder<*, NoOpSearchBehaviour>::ignoringCase.name + + @Suppress("unused", "UNUSED_VALUE") + private fun ambiguityTest() { + val a1: Expect = notImplemented() + + a1.contains.atLeast(1).value(1) + a1.contains.atMost(2).values("a", 1) + a1.contains.notOrAtMost(2).regex("h|b") + a1.contains.exactly(2).regex("h|b", "b") + a1.contains.atLeast(2).regex(Regex("bla")) + a1.contains.atLeast(2).regex(Regex("bla"), Regex("b")) + a1.contains.atLeast(2).elementsOf(listOf("a", 2)) + + a1.contains.ignoringCase.atLeast(1).value("a") + a1.contains.ignoringCase.atLeast(1).values("a", 'b') + a1.contains.ignoringCase.atLeast(1).regex("a") + a1.contains.ignoringCase.atLeast(1).regex("a", "bl") + a1.contains.ignoringCase.atLeast(1).elementsOf(listOf(1, 2)) + + // skip atLeast + a1.contains.ignoringCase.value("a") + a1.contains.ignoringCase.values("a", 'b') + a1.contains.ignoringCase.regex("a") + a1.contains.ignoringCase.regex("a", "bl") + //TODO add to infix as well as fluent +// a1.contains.ignoringCase.elementsOf(listOf(1, 2))("a", "bl") + } } diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceAssertions.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceAssertions.kt new file mode 100644 index 000000000..46370d8fe --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceAssertions.kt @@ -0,0 +1,324 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.NotCheckerOption +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.impl.NotCheckerOptionImpl +import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.domain.builders.ExpectImpl +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains +import ch.tutteli.atrium.domain.creating.charsequence.contains.searchbehaviours.NoOpSearchBehaviour +import ch.tutteli.atrium.domain.creating.charsequence.contains.searchbehaviours.NotSearchBehaviour + + +/** + * Creates a [CharSequenceContains.Builder] based on this [Expect] which allows to define + * a sophisticated `contains` assertion. + * + * @param o The filler object [o]; use [O] in case you are in an [Expect] context due to a Kotlin bug + * (see type alias for more information) + * + * @return The newly created builder. + */ +infix fun Expect.contains( + @Suppress("UNUSED_PARAMETER") o: o +): CharSequenceContains.Builder = ExpectImpl.charSequence.containsBuilder(this) + +/** + * Creates a [CharSequenceContains.Builder] based on this [Expect] which allows to define + * more sophisticated `contains not` assertion. + * + * @return The newly created builder. + */ +infix fun Expect.contains( + @Suppress("UNUSED_PARAMETER") not: not +): NotCheckerOption = NotCheckerOptionImpl(ExpectImpl.charSequence.containsNotBuilder(this)) + + +/** + * Expects that the subject of the assertion (a [CharSequence]) contains the [expected]'s [toString] representation. + * + * It is a shortcut for `contains o atLeast 1 value expected`. + * + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed (this + * function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * @throws IllegalArgumentException in case [expected] is not a + * [CharSequence], [Number] or [Char]. + */ +infix fun Expect.contains(expected: Any): Expect = + this contains O atLeast 1 value expected + +/** + * Expects that the subject of the assertion (a [CharSequence]) contains the [toString] representation of the + * given [values] using a non disjoint search. + * + * It is a shortcut for `contains o atLeast 1 the Values(expected, *otherExpected)`. + * + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed (this + * function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * By non disjoint is meant that `'aa'` in `'aaaa'` is found three times and not only two times. + * Also notice, that it does not search for unique matches. Meaning, if the input of the search is `'a'` and + * [Values.expected] is defined as `'a'` and one [Values.otherExpected] is defined as `'a'` as well, then both match, + * even though they match the same sequence in the input of the search. Use the property `contains` to create + * a more sophisticated `contains` assertion where you can use options such as [atLeast], [atMost] and [exactly] + * to control the number of occurrences you expect. + * + * Meaning you might want to use: + * `contains o exactly 2 value 'a'` + * instead of: + * `contains Values('a', 'a')` + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * @throws IllegalArgumentException in case one of the [values] is not a + * [CharSequence], [Number] or [Char]. + */ +infix fun Expect.contains(values: Values): Expect = + this contains O atLeast 1 the values + +/** + * Expects that the subject of the assertion (a [CharSequence]) does not contain [expected]'s [toString] representation. + * + * It is a shortcut for `contains not value expected`. + * + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed (this + * function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.containsNot(expected: Any) = + this contains not value expected + +/** + * Expects that the subject of the assertion (a [CharSequence]) does not contain the [toString] representation + * of the given [values]. + * + * It is a shortcut for `contains not the Values(expected, *otherExpected)`. + * + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed (this + * function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.containsNot(values: Values) = + this contains not the values + +/** + * Expects that the subject of the assertion (a [CharSequence]) contains a sequence which matches the given + * regular expression [pattern]. + * + * It is a shortcut for `contains o atLeast 1 regex pattern`. + * + * @param pattern The pattern which is expected to have a match against the input of the search. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.containsRegex(pattern: String): Expect = + this contains O atLeast 1 regex pattern + +/** + * Expects that the subject of the assertion (a [CharSequence]) contains a sequence which matches the given + * regular expression [pattern]. + * + * It is a shortcut for `contains o atLeast 1 regex pattern`. + * + * @param pattern The pattern which is expected to have a match against the input of the search. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.contains(pattern: Regex): Expect = + this contains O atLeast 1 matchFor pattern + +/** + * Expects that the subject of the assertion (a [CharSequence]) contains a sequence which matches the given + * regular expression [patterns], using a non disjoint search. + * + * It is a shortcut for `contains o atLeast 1 the RegexPatterns(pattern, *otherPatterns)`. + * + * By non disjoint is meant that `'aa'` in `'aaaa'` is found three times and not only two times. + * Also notice, that it does not search for unique matches. Meaning, if the input of the search is `'ab'` and + * [RegexPatterns.expected] is defined as `'a(b)?'` and one of the [RegexPatterns.otherExpected] is defined + * as `'a(b)?'` as well, then both match, even though they match the same sequence in the input of the search. + * Use an option such as [atLeast], [atMost] and [exactly] to control the number of occurrences you expect. + * + * Meaning you might want to use: + * `contains o exactly 2 regex "a(b)?"` + * instead of: + * `contains o atLeast 1 the RegexPatterns("a(b)?", "a(b)?")` + * + * @param patterns The patterns which are expected to have a match against the input of the search. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.containsRegex(patterns: RegexPatterns): Expect = + this contains O atLeast 1 the patterns + +/** + * Expects that the subject of the assertion (a [CharSequence]) contains a sequence which matches the given + * regular expression [patterns], using a non disjoint search. + * + * It is a shortcut for `contains o atLeast 1 regex All(pattern, *otherPatterns)`. + * + * By non disjoint is meant that `'aa'` in `'aaaa'` is found three times and not only two times. + * Also notice, that it does not search for unique matches. Meaning, if the input of the search is `'ab'` and + * [RegexPatterns.expected] is defined as `'a(b)?'` and one of the [RegexPatterns.otherExpected] is defined + * as `'a(b)?'` as well, then both match, even though they match the same sequence in the input of the search. + * Use an option such as [atLeast], [atMost] and [exactly] to control the number of occurrences you expect. + * + * Meaning you might want to use: + * `contains o exactly 2 regex "a(b)?"` + * instead of: + * `contains o atLeast 1 the RegexPatterns("a(b)?", "a(b)?")` + * + * @param patterns The patterns which are expected to have a match against the input of the search. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.contains(patterns: All): Expect = + this contains O atLeast 1 matchFor patterns +/** + * Expects that the subject of the assertion (a [CharSequence]) starts with [expected]. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.startsWith(expected: CharSequence) = + addAssertion(ExpectImpl.charSequence.startsWith(this, expected)) + +/** + * Expects that the subject of the assertion (a [CharSequence]) starts with [expected]. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * + * @since 0.10.0 + */ +infix fun Expect.startsWith(expected: Char) = + o startsWith expected.toString() + +/** + * Expects that the subject of the assertion (a [CharSequence]) does not start with [expected]. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.startsNotWith(expected: CharSequence) = + addAssertion(ExpectImpl.charSequence.startsNotWith(this, expected)) + +/** + * Expects that the subject of the assertion (a [CharSequence]) does not start with [expected]. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * + * @since 0.10.0 + */ +infix fun Expect.startsNotWith(expected: Char) = + o startsNotWith expected.toString() + + +/** + * Expects that the subject of the assertion (a [CharSequence]) ends with [expected]. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.endsWith(expected: CharSequence) = + addAssertion(ExpectImpl.charSequence.endsWith(this, expected)) + +/** + * Expects that the subject of the assertion (a [CharSequence]) ends with [expected]. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * + * @since 0.10.0 + */ +infix fun Expect.endsWith(expected: Char) = + o endsWith expected.toString() + +/** + * Expects that the subject of the assertion (a [CharSequence]) does not end with [expected]. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.endsNotWith(expected: CharSequence) = + addAssertion(ExpectImpl.charSequence.endsNotWith(this, expected)) + +/** + * Expects that the subject of the assertion (a [CharSequence]) does not end with [expected]. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * + * @since 0.10.0 + */ +infix fun Expect.endsNotWith(expected: Char) = + o endsNotWith expected.toString() + + +/** + * Expects that the subject of the assertion (a [CharSequence]) [CharSequence].[kotlin.text.isEmpty]. + * + * @param Empty Has to be `Empty`. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.toBe(@Suppress("UNUSED_PARAMETER") Empty: Empty) = + addAssertion(ExpectImpl.charSequence.isEmpty(this)) + +/** + * Expects that the subject of the assertion (a [CharSequence]) [CharSequence].[kotlin.text.isNotEmpty]. + * + * @param Empty Has to be `Empty`. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.notToBe(@Suppress("UNUSED_PARAMETER") Empty: Empty) = + addAssertion(ExpectImpl.charSequence.isNotEmpty(this)) + +/** + * Expects that the subject of the assertion (a [CharSequence]) [CharSequence].[kotlin.text.isNotBlank]. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.notToBe(@Suppress("UNUSED_PARAMETER") Blank: Blank) = + addAssertion(ExpectImpl.charSequence.isNotBlank(this)) + +/** + * Expects that the subject of the assertion (a [CharSequence]) matches the given [expected] [Regex]. + * + * In contrast to [containsRegex] it does not look for a partial match but for an entire match. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * + * @since 0.10.0 + */ +infix fun Expect.matches(expected: Regex) = + addAssertion(ExpectImpl.charSequence.matches(this, expected)) + +/** + * Expects that the subject of the assertion (a [CharSequence]) mismatches the given [expected] [Regex]. + * + * In contrast to `containsNot.regex` it does not look for a partial match but for an entire match. + * + * @return This assertion container to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * + * @since 0.10.0 + */ +infix fun Expect.mismatches(expected: Regex) = + addAssertion(ExpectImpl.charSequence.mismatches(this, expected)) diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceContainsCheckers.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceContainsCheckers.kt new file mode 100644 index 000000000..be8e9797b --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceContainsCheckers.kt @@ -0,0 +1,90 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.* +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.impl.* +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains.SearchBehaviour + +/** + * Restricts a `contains` assertion by specifying that the number of occurrences of the value which we are looking + * for occurs `at least` number of [times] within the search input. + * + * @param times The number which the check will compare against the actual number of times an expected value is + * found in the input of the search. + * + * @return The newly created builder. + * @throws IllegalArgumentException In case [times] is smaller than zero. + * @throws IllegalArgumentException In case [times] equals to zero; use [containsNot] instead. + */ +infix fun CharSequenceContains.Builder.atLeast( + times: Int +): AtLeastCheckerOption = AtLeastCheckerOptionImpl(times, this) + +/** + * Restricts a `contains at least` assertion by specifying that the number of occurrences of the value which we + * are looking for occurs `at most` number of [times] within the search input. + * + * The resulting restriction will be a `contains at least but at most` assertion. + * + * @param times The number which the check will compare against the actual number of times an expected value is + * found in the input of the search. + * + * @return The newly created builder. + * @throws IllegalArgumentException In case [times] is smaller than zero. + * @throws IllegalArgumentException In case [times] equals to zero; use [containsNot] instead. + * @throws IllegalArgumentException In case [times] of this `at most` restriction equals to the number of the + * `at least` restriction; use the [exactly] restriction instead. + */ +infix fun AtLeastCheckerOption.butAtMost( + times: Int +): ButAtMostCheckerOption = ButAtMostCheckerOptionImpl(times, this, containsBuilder) + +/** + * Restricts a `contains` assertion by specifying that the number of occurrences of the value which we + * are looking for occurs `exactly` number of [times] within the search input. + * + * @param times The number which the check will compare against the actual number of times an expected value is + * found in the input of the search. + * + * @return The newly created builder. + * @throws IllegalArgumentException In case [times] is smaller than zero. + * @throws IllegalArgumentException In case [times] equals to zero; use [containsNot] instead. + */ +infix fun CharSequenceContains.Builder.exactly( + times: Int +): ExactlyCheckerOption = ExactlyCheckerOptionImpl(times, this) + +/** + * Restricts a `contains` assertion by specifying that the number of occurrences of the value which we + * are looking for occurs `at least` once but `at most` number of [times] within the search input. + * + * If you want to use a higher lower bound than one, then use `atLeast(2).butAtMost(3)` instead of `atMost(3)`. + * And in case you want to state that it is either not contained at all or at most a certain number of times, + * then use `notOrAstMost(2)` instead. + * + * @param times The number which the check will compare against the actual number of times an expected value is + * found in the input of the search. + * + * @return The newly created builder. + * @throws IllegalArgumentException In case [times] is smaller than zero. + * @throws IllegalArgumentException In case [times] equals to zero; use [containsNot] instead. + * @throws IllegalArgumentException In case [times] equals to one; use [exactly] instead. + */ +infix fun CharSequenceContains.Builder.atMost( + times: Int +): AtMostCheckerOption = AtMostCheckerOptionImpl(times, this) + +/** + * Restricts a `contains` assertion by specifying that the number of occurrences of the value which we + * are looking for occurs `not at all or at most` number of [times] within the search input. + * + * @param times The number which the check will compare against the actual number of times an expected value is + * found in the input of the search. + * + * @return The newly created builder. + * @throws IllegalArgumentException In case [times] is smaller than zero. + * @throws IllegalArgumentException In case [times] equals to zero; use [containsNot] instead. + */ +infix fun CharSequenceContains.Builder.notOrAtMost( + times: Int +): NotOrAtMostCheckerOption = NotOrAtMostCheckerOptionImpl(times, this) diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceContainsCreators.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceContainsCreators.kt new file mode 100644 index 000000000..06eea54f4 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceContainsCreators.kt @@ -0,0 +1,376 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.domain.builders.ExpectImpl +import ch.tutteli.atrium.domain.builders.creating.basic.contains.addAssertion +import ch.tutteli.atrium.domain.builders.utils.toVarArg +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains.Builder +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains.CheckerOption +import ch.tutteli.atrium.domain.creating.charsequence.contains.searchbehaviours.IgnoringCaseSearchBehaviour +import ch.tutteli.atrium.domain.creating.charsequence.contains.searchbehaviours.NoOpSearchBehaviour +import kotlin.jvm.JvmName + +/** + * Finishes the specification of the sophisticated `contains` assertion where the [expected] object shall be searched, + * using a non disjoint search. + * + * Delegates to `the Values(expected)`. + * + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed (this + * function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * By non disjoint is meant that 'aa' in 'aaaa' is found three times and not only two times. + * + * @param expected The value which is expected to be contained within the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * @throws IllegalArgumentException in case [expected] is not a [CharSequence], [Number] or [Char]. + */ +infix fun CheckerOption.value(expected: Any): Expect = + this the Values(expected) + +/** + * Finishes the specification of the sophisticated `contains` assertion where the given [values] + * shall be searched, using a non disjoint search. + * + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed (this + * function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * By non disjoint is meant that `'aa'` in `'aaaa'` is found three times and not only two times. + * Also notice, that it does not search for unique matches. Meaning, if the input of the search is `'a'` and + * [Values.expected] is defined as `'a'` and one [Values.otherExpected] is defined as `'a'` as well, then both match, + * even though they match the same sequence in the input of the search. Use an option such as + * [atLeast], [atMost] and [exactly] to control the number of occurrences you expect. + * + * Meaning you might want to use: + * `to contain exactly 2 the value 'a'` + * instead of: + * `to contain atLeast 1 the Values('a', 'a')` + * + * @param values The values which are expected to be contained within the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * @throws IllegalArgumentException in case one of the [values] is not a [CharSequence], [Number] or [Char]. + */ +infix fun CheckerOption.the(values: Values): Expect = + addAssertion(ExpectImpl.charSequence.contains.values(this, values.toList())) + + +/** + * Finishes the specification of the sophisticated `contains` assertion where the [expected] value shall be searched + * (ignoring case), using a non disjoint search. + * + * Delegates to `the Values(expected)`. + * + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed (this + * function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * By non disjoint is meant that 'aa' in 'aaaa' is found three times and not only two times. + * + * @param expected The value which is expected to be contained within the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * @throws IllegalArgumentException in case [expected] is not a [CharSequence], [Number] or [Char]. + */ +@JvmName("valueIgnoringCase") +infix fun CheckerOption.value(expected: Any): Expect = + this the Values(expected) + +/** + * Finishes the specification of the sophisticated `contains` assertion where the [values] + * shall be searched (ignoring case), using a non disjoint search. + * + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed (this + * function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * By non disjoint is meant that `'aa'` in `'aaaa'` is found three times and not only two times. + * Also notice, that it does not search for unique matches. Meaning, if the input of the search is `'a'` and + * [Values.expected] is defined as `'a'` and one [Values.otherExpected] is defined as `'a'` as well, then both match, + * even though they match the same sequence in the input of the search. Use an option such as + * [atLeast], [atMost] and [exactly] to control the number of occurrences you expect. + * + * Meaning you might want to use: + * `to contain ignoring case exactly 2 the value 'a'` + * instead of: + * `to contain ignoring case atLeast 1 the Values('a', 'a')` + * + * @param values The values which are expected to be contained within the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * @throws IllegalArgumentException in case one of the [values] is not a [CharSequence], [Number] or [Char]. + */ +@JvmName("valuesIgnoringCase") +infix fun CheckerOption.the(values: Values): Expect = + addAssertion(ExpectImpl.charSequence.contains.valuesIgnoringCase(this, values.toList())) + +/** + * Finishes the specification of the sophisticated `contains` assertion where the [expected] value shall be searched + * (ignoring case), using a non disjoint search where it needs to be contained at least once. + * + * Delegates to `atLeast 1 value expected`. + * + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed (this + * function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * By non disjoint is meant that 'aa' in 'aaaa' is found three times and not only two times. + * + * @param expected The value which is expected to be contained within the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * @throws IllegalArgumentException in case [expected] is not a [CharSequence], [Number] or [Char]. + */ +infix fun Builder.value(expected: Any): Expect = + this atLeast 1 value expected + +/** + * Finishes the specification of the sophisticated `contains` assertion where the [values] + * shall be searched (ignoring case), using a non disjoint search + * where each need to be contained at least once. + * + * Delegates to `atLeast 1 the value` + * + * By non disjoint is meant that `'aa'` in `'aaaa'` is found three times and not only two times. + * Also notice, that it does not search for unique matches. Meaning, if the input of the search is `'a'` and + * [Values.expected] is defined as `'a'` and one [Values.otherExpected] is defined as `'a'` as well, then both match, + * even though they match the same sequence in the input of the search. + * Use an option such as [atLeast], [atMost] and [exactly] to control the number of occurrences you expect. + * + * Meaning you might want to use: + * `to contain ignoring case exactly 2 the value 'a'` + * instead of: + * `to contain ignoring case atLeast 1 the Values('a', 'a')` + * + * @param values The values which are expected to be contained within the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * @throws IllegalArgumentException in case one of the [values] is not a [CharSequence], [Number] or [Char]. + */ +infix fun Builder.the(values: Values): Expect = + this atLeast 1 the values + +/** + * Finishes the specification of the sophisticated `contains` assertion where the given regular expression [pattern] + * is expected to have a match, using a non disjoint search. + * + * Delegates to `the RegexPatterns(pattern)`. + * + * @param pattern The pattern which is expected to have a match against the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun CheckerOption.regex(pattern: String): Expect = + this the RegexPatterns(pattern) + +/** + * Finishes the specification of the sophisticated `contains` assertion where the given [Regex] [pattern] + * is expected to have a match. + * + * Delegates to `All(pattern)` + * + * @param pattern The pattern which is expected to have a match against the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * + * @since 0.10.0 + */ +infix fun CheckerOption.matchFor( + pattern: Regex +): Expect = this matchFor All(pattern) + +/** + * Finishes the specification of the sophisticated `contains` assertion where the given regular expression [patterns] + * are expected to have a match, using a non disjoint search. + * + * By non disjoint is meant that `'aa'` in `'aaaa'` is found three times and not only two times. + * Also notice, that it does not search for unique matches. Meaning, if the input of the search is `'ab'` and + * [patterns].[pattern][RegexPatterns.expected] is defined as `'a(b)?'` and one of the + * [patterns].[otherPatterns][RegexPatterns.otherExpected] is defined as `'a(b)?'` as well, then both match, even though + * they match the same sequence in the input of the search. + * Use an option such as [atLeast], [atMost] and [exactly] to control the number of occurrences you expect. + * + * Meaning you might want to use: + * `to contain exactly 2 the regex 'a(b)?'` + * instead of: + * `to contain atLeast 1 the RegexPatterns('a(b)?', 'a(b)?')` + * + * @param patterns The patterns which are expected to have a match against the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun CheckerOption.the(patterns: RegexPatterns): Expect = + addAssertion(ExpectImpl.charSequence.contains.regex(this, patterns.toList())) + +/** + * Finishes the specification of the sophisticated `contains` assertion where the given [Regex] [patterns] + * are expected to have a match, using a non disjoint search. + * + * By non disjoint is meant that `'aa'` in `'aaaa'` is found three times and not only two times. + * Also notice, that it does not search for unique matches. Meaning, if the input of the search is `'ab'` and the first + * pattern in [patterns] is defined as `'a(b)?'` and one of the other patterns is defined as `'a(b)?'` as well, + * then both match, even though they match the same sequence in the input of the search. + * Use an option such as [atLeast], [atMost] and [exactly] to + * control the number of occurrences you expect. + * + * Meaning you might want to use: + * `contains o exactly 2 matchFor Regex("a(b)?")` + * instead of: + * `contains o atLeast 1 matchFor All(Regex("a(b)?"), Regex("a(b)?")) + * + * @param patterns The patterns which are expected to have a match against the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * + * @since 0.10.0 + */ +infix fun CheckerOption.matchFor(patterns: All): Expect = + addAssertion(ExpectImpl.charSequence.contains.regex(this, patterns.toList())) + + + +/** + * Finishes the specification of the sophisticated `contains` assertion where the given regular expression [pattern] + * is expected to have a match (ignoring case), using a non disjoint search. + * + * Delegates to `the RegexPatterns(pattern)`. + * + * @param pattern The patterns which is expected to have a match against the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +@JvmName("regexIgnoringCase") +infix fun CheckerOption.regex(pattern: String): Expect = + this the RegexPatterns(pattern) + +/** + * Finishes the specification of the sophisticated `contains` assertion where the given regular expression [patterns] + * are expected to have a match (ignoring case), using a non disjoint search. + * + * By non disjoint is meant that `'aa'` in `'aaaa'` is found three times and not only two times. + * Also notice, that it does not search for unique matches. Meaning, if the input of the search is `'ab'` and + * [patterns].[pattern][RegexPatterns.expected] is defined as `'a(b)?'` and one of the + * [patterns].[otherPatterns][RegexPatterns.otherExpected] is defined as `'a(b)?'` as well, then both match, even though + * they match the same sequence in the input of the search. + * Use an option such as [atLeast], [atMost] and [exactly] to control the number of occurrences you expect. + * + * Meaning you might want to use: + * `to contain ignoring case exactly 2 the regex 'a(b)?'` + * instead of: + * `to contain ignoring case atLeast 1 the RegexPatterns('a(b)?', 'a(b)?')` + * + * @param patterns The patterns which are expected to have a match against the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +@JvmName("regexIgnoringCase") +infix fun CheckerOption.the(patterns: RegexPatterns): Expect = + addAssertion(ExpectImpl.charSequence.contains.regexIgnoringCase(this, patterns.toList())) + +/** + * Finishes the specification of the sophisticated `contains` assertion where the given regular expression [pattern] + * is expected to have at least one match (ignoring case), using a non disjoint search. + * + * Delegates to `atLeast 1 regex pattern`. + * + * @param pattern The patterns which is expected to have a match against the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Builder.regex(pattern: String): Expect = + this atLeast 1 regex pattern + +/** + * Finishes the specification of the sophisticated `contains` assertion where the given regular expression [patterns] + * are expected to have at least one match (ignoring case), using a non disjoint search. + * + * Delegates to `atLeast 1 the patterns`. + * + * By non disjoint is meant that `'aa'` in `'aaaa'` is found three times and not only two times. + * Also notice, that it does not search for unique matches. Meaning, if the input of the search is `'ab'` and + * [patterns].[pattern][RegexPatterns.expected] is defined as `'a(b)?'` and one of the + * [patterns].[otherPatterns][RegexPatterns.otherExpected] is defined as `'a(b)?'` as well, then both match, even though + * they match the same sequence in the input of the search. + * Use an option such as [atLeast], [atMost] and [exactly] to control the number of occurrences you expect. + * + * Meaning you might want to use: + * `to contain ignoring case exactly 2 the regex 'a(b)?'` + * instead of: + * `to contain ignoring case atLeast 1 the RegexPatterns('a(b)?', 'a(b)?')` + * + * @param patterns The patterns which are expected to have a match against the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Builder.the(patterns: RegexPatterns): Expect = + this atLeast 1 the patterns + +/** + * Finishes the specification of the sophisticated `contains` assertion where all elements of the [expectedIterable] + * shall be searched, using a non disjoint search. + * + * Delegates to `the Values(expectedIterable.first(), *expectedIterable.drop(1).toTypedArray())` + * (see [the] for more information). + * + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed (this + * function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * By non disjoint is meant that 'aa' in 'aaaa' is found three times and not only two times. + * + * @param expectedIterable The [Iterable] whose elements are expected to be contained within the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * @throws IllegalArgumentException in case [expectedIterable] is not a [CharSequence], [Number] or [Char] or the given + * [expectedIterable] does not have elements (is empty). + * + * @since 0.10.0 + */ +infix fun CheckerOption.elementsOf( + expectedIterable: Iterable +): Expect { + val (first, rest) = toVarArg(expectedIterable) + return this the Values(first, *rest) +} + + +/** + * Finishes the specification of the sophisticated `contains` assertion where all elements of the [expectedIterable] + * shall be searched (ignoring case), using a non disjoint search. + * + * Delegates to `the Values(expectedIterable.first(), *expectedIterable.drop(1).toTypedArray())` + * (see [the] for more information). + * + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed (this + * function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * By non disjoint is meant that 'aa' in 'aaaa' is found three times and not only two times. + * + * @param expectedIterable The [Iterable] whose elements are expected to be contained within the input of the search. + * + * @return The [Expect] for which the assertion was built to support a fluent API. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + * @throws IllegalArgumentException in case [expectedIterable] is not a [CharSequence], [Number] or [Char] or the given + * [expectedIterable] does not have elements (is empty). + * + * @since 0.10.0 + */ +@JvmName("elementsOfIgnoringCase") +infix fun CheckerOption.elementsOf( + expectedIterable: Iterable +): Expect { + val (first, rest) = toVarArg(expectedIterable) + return this the Values(first, *rest) +} diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceContainsSearchBehaviours.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceContainsSearchBehaviours.kt new file mode 100644 index 000000000..122367303 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/charSequenceContainsSearchBehaviours.kt @@ -0,0 +1,33 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.NotCheckerOption +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.impl.NotCheckerOptionImpl +import ch.tutteli.atrium.domain.builders.ExpectImpl +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains +import ch.tutteli.atrium.domain.creating.charsequence.contains.searchbehaviours.IgnoringCaseSearchBehaviour +import ch.tutteli.atrium.domain.creating.charsequence.contains.searchbehaviours.NoOpSearchBehaviour +import ch.tutteli.atrium.domain.creating.charsequence.contains.searchbehaviours.NotSearchBehaviour + +/** + * Defines that the search behaviour `ignore case` shall be applied to this sophisticated `contains` assertion. + * + * @param case Has to be `case`. + * + * @return The newly created builder. + */ +infix fun CharSequenceContains.Builder.ignoring( + @Suppress("UNUSED_PARAMETER") case: case +): CharSequenceContains.Builder = + ExpectImpl.charSequence.contains.searchBehaviours.ignoringCase(this) + +/** + * Defines that the search behaviour `ignore case` shall be applied to this sophisticated `contains not` assertion. + * + * @param case Has to be `case`. + * + * @return The newly created builder. + */ +infix fun NotCheckerOption.ignoring( + @Suppress("UNUSED_PARAMETER") case: case +): NotCheckerOption = + NotCheckerOptionImpl(containsBuilder ignoring case) diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/AtLeastCheckerOption.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/AtLeastCheckerOption.kt new file mode 100644 index 000000000..3b6cc2866 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/AtLeastCheckerOption.kt @@ -0,0 +1,14 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders + +import ch.tutteli.atrium.domain.builders.creating.charsequence.contains.builders.WithTimesCheckerOption +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains + +/** + * Represents the extension point for another option after a `contains at least`-check within a sophisticated + * `contains` assertion building process for [CharSequence]. + * + * @param T The input type of the search. + * @param S The search behaviour which should be applied for the input of the search. + */ +interface AtLeastCheckerOption + : WithTimesCheckerOption diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/AtMostCheckerOption.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/AtMostCheckerOption.kt new file mode 100644 index 000000000..b9b942050 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/AtMostCheckerOption.kt @@ -0,0 +1,13 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders + +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains + +/** + * Represents the extension point for another option after a `contains at least once but at most`-check within + * a sophisticated `contains` assertion building process for [CharSequence]. + * + * @param T The input type of the search. + * @param S The search behaviour which should be applied for the input of the search. + */ +interface AtMostCheckerOption + : CharSequenceContains.CheckerOption diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/ButAtMostCheckerOption.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/ButAtMostCheckerOption.kt new file mode 100644 index 000000000..a67f28ad4 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/ButAtMostCheckerOption.kt @@ -0,0 +1,13 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders + +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains + +/** + * Represents the extension point for another option after a `contains at least but at most`-check within + * a sophisticated `contains` assertion building process for [CharSequence]. + * + * @param T The input type of the search. + * @param S The search behaviour which should be applied for the input of the search. + */ +interface ButAtMostCheckerOption + : CharSequenceContains.CheckerOption diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/ExactlyCheckerOption.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/ExactlyCheckerOption.kt new file mode 100644 index 000000000..f0175cbe2 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/ExactlyCheckerOption.kt @@ -0,0 +1,13 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders + +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains + +/** + * Represents the extension point for another option after a `contains exactly`-check within + * a sophisticated `contains` assertion building process for [CharSequence]. + * + * @param T The input type of the search. + * @param S The search behaviour which should be applied for the input of the search. + */ +interface ExactlyCheckerOption + : CharSequenceContains.CheckerOption diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/NotCheckerOption.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/NotCheckerOption.kt new file mode 100644 index 000000000..7895f6c3e --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/NotCheckerOption.kt @@ -0,0 +1,13 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders + +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains + +/** + * Represents the extension point for another option after a `contains not at all`-check within + * a sophisticated `contains` assertion building process for [CharSequence]. + * + * @param T The input type of the search. + * @param S The search behaviour which should be applied for the input of the search. + */ +interface NotCheckerOption + : CharSequenceContains.CheckerOption diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/NotOrAtMostCheckerOption.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/NotOrAtMostCheckerOption.kt new file mode 100644 index 000000000..105459299 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/NotOrAtMostCheckerOption.kt @@ -0,0 +1,13 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders + +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains + +/** + * Represents the extension point for another option after a `contains not or at most`-check within + * a sophisticated `contains` assertion building process for [CharSequence]. + * + * @param T The input type of the search. + * @param S The search behaviour which should be applied for the input of the search. + */ +interface NotOrAtMostCheckerOption + : CharSequenceContains.CheckerOption diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/AtLeastCheckerOptionImpl.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/AtLeastCheckerOptionImpl.kt new file mode 100644 index 000000000..d90b1662d --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/AtLeastCheckerOptionImpl.kt @@ -0,0 +1,29 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.impl + +import ch.tutteli.atrium.api.infix.en_GB.atLeast +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.AtLeastCheckerOption +import ch.tutteli.atrium.domain.builders.creating.charsequence.contains.builders.AtLeastCheckerOptionBase +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains + +/** + * Represents the builder of a `contains at least`-check within the fluent API of a sophisticated + * `contains` assertion for [CharSequence]. + * + * @param T The input type of the search. + * @param S The search behaviour which should be applied for the input of the search. + * + * @constructor Represents the builder of a `contains at least` check within the fluent API of a sophisticated + * `contains` assertion for [CharSequence]. + * @param times The number which the check will compare against the actual number of times an expected object is + * found in the input of the search. + * @param containsBuilder The previously used [CharSequenceContains.Builder]. + */ +internal class AtLeastCheckerOptionImpl( + times: Int, + containsBuilder: CharSequenceContains.Builder +) : AtLeastCheckerOptionBase( + times, + containsBuilder, + nameContainsNotValuesFun(), + { "`${containsBuilder::atLeast.name} $it`" } +), AtLeastCheckerOption diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/AtMostCheckerOptionImpl.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/AtMostCheckerOptionImpl.kt new file mode 100644 index 000000000..4d361dcd8 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/AtMostCheckerOptionImpl.kt @@ -0,0 +1,33 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.impl + +import ch.tutteli.atrium.api.infix.en_GB.atLeast +import ch.tutteli.atrium.api.infix.en_GB.atMost +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.AtMostCheckerOption +import ch.tutteli.atrium.api.infix.en_GB.exactly +import ch.tutteli.atrium.domain.builders.creating.charsequence.contains.builders.AtMostCheckerOptionBase +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains + +/** + * Represents the builder of a `contains at least once but at most` check within the fluent API of a + * sophisticated `contains` assertion for [CharSequence]. + * + * @param T The input type of the search. + * @param S The search behaviour which should be applied for the input of the search. + * + * @constructor Represents the builder of a `contains at least once but at most` check within the fluent API of a + * sophisticated `contains` assertion for [CharSequence]. + * @param times The number which the check will compare against the actual number of times an expected object is + * found in the input of the search. + * @param containsBuilder The previously used [CharSequenceContains.Builder]. + */ +internal class AtMostCheckerOptionImpl( + times: Int, + containsBuilder: CharSequenceContains.Builder +) : AtMostCheckerOptionBase( + times, + containsBuilder, + nameContainsNotValuesFun(), + { "`${containsBuilder::atMost.name} $it`" }, + { "`${containsBuilder::atLeast.name} $it`" }, + { "`${containsBuilder::exactly.name} $it`" } +), AtMostCheckerOption diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/ButAtMostCheckerOptionImpl.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/ButAtMostCheckerOptionImpl.kt new file mode 100644 index 000000000..be601909c --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/ButAtMostCheckerOptionImpl.kt @@ -0,0 +1,39 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.impl + +import ch.tutteli.atrium.api.infix.en_GB.atLeast +import ch.tutteli.atrium.api.infix.en_GB.atMost +import ch.tutteli.atrium.api.infix.en_GB.butAtMost +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.AtLeastCheckerOption +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.ButAtMostCheckerOption +import ch.tutteli.atrium.api.infix.en_GB.exactly +import ch.tutteli.atrium.domain.builders.creating.charsequence.contains.builders.ButAtMostCheckerOptionBase +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains + +/** + * Represents the builder of the second step of a `contains at least but at most` check within the + * fluent API of a sophisticated `contains` assertion for [CharSequence]. + * + * @param T The input type of the search. + * @param S The search behaviour which should be applied to the input of the search. + * + * @constructor Represents the builder of the second step of a `contains at least but at most` check within the + * fluent API of a sophisticated `contains` assertion for [CharSequence]. + * @param times The number which the check will compare against the actual number of times an expected object is + * found in the input of the search. + * @param containsBuilder The previously used [CharSequenceContains.Builder]. + */ +internal class ButAtMostCheckerOptionImpl( + times: Int, + atLeastBuilder: AtLeastCheckerOption, + containsBuilder: CharSequenceContains.Builder +) : ButAtMostCheckerOptionBase( + times, + atLeastBuilder, + containsBuilder, + nameContainsNotValuesFun(), + { l, u -> "`${containsBuilder::atLeast.name} $l ${atLeastBuilder::butAtMost.name} $u`" }, + { "`${containsBuilder::atMost.name} $it`" }, + { "`${containsBuilder::atLeast.name} $it`" }, + { "`${atLeastBuilder::butAtMost.name} $it`" }, + { "`${containsBuilder::exactly.name} $it`" } +), ButAtMostCheckerOption diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/ExactlyCheckerOptionImpl.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/ExactlyCheckerOptionImpl.kt new file mode 100644 index 000000000..bcc2e9822 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/ExactlyCheckerOptionImpl.kt @@ -0,0 +1,29 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.impl + +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.ExactlyCheckerOption +import ch.tutteli.atrium.api.infix.en_GB.exactly +import ch.tutteli.atrium.domain.builders.creating.charsequence.contains.builders.ExactlyCheckerOptionBase +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains + +/** + * Represents the builder of a `contains exactly` check within the fluent API of a sophisticated + * `contains` assertion for [CharSequence]. + * + * @param T The input type of the search. + * @param S The search behaviour which should be applied for the input of the search. + * + * @constructor Represents the builder of a `contains exactly` check within the fluent API of a sophisticated + * `contains` assertion for [CharSequence]. + * @param times The number which the check will compare against the actual number of times an expected object is + * found in the input of the search. + * @param containsBuilder The previously used [CharSequenceContains.Builder]. + */ +internal class ExactlyCheckerOptionImpl( + times: Int, + containsBuilder: CharSequenceContains.Builder +) : ExactlyCheckerOptionBase( + times, + containsBuilder, + nameContainsNotValuesFun(), + { "`${containsBuilder::exactly.name} $it`" } +), ExactlyCheckerOption diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/NotCheckerOptionImpl.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/NotCheckerOptionImpl.kt new file mode 100644 index 000000000..81aac3823 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/NotCheckerOptionImpl.kt @@ -0,0 +1,21 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.impl + +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.NotCheckerOption +import ch.tutteli.atrium.domain.builders.creating.charsequence.contains.builders.NotCheckerOptionBase +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains + +/** + * Represents the builder of a `contains not at all` check within the fluent API of a sophisticated + * `contains` assertion for [CharSequence]. + * + * @param T The input type of the search. + * @param S The search behaviour which should be applied for the input of the search. + * + * @constructor Represents the builder of a `contains not at all` check within the fluent API of a sophisticated + * `contains` assertion for [CharSequence]. + * @param containsBuilder The previously used [CharSequenceContains.Builder]. + */ +internal class NotCheckerOptionImpl( + containsBuilder: CharSequenceContains.Builder +) : NotCheckerOptionBase(containsBuilder), + NotCheckerOption diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/NotOrAtMostCheckerOptionImpl.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/NotOrAtMostCheckerOptionImpl.kt new file mode 100644 index 000000000..bb2b455b6 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/NotOrAtMostCheckerOptionImpl.kt @@ -0,0 +1,29 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.impl + +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.NotOrAtMostCheckerOption +import ch.tutteli.atrium.api.infix.en_GB.notOrAtMost +import ch.tutteli.atrium.domain.builders.creating.charsequence.contains.builders.NotOrAtMostCheckerOptionBase +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains + +/** + * Represents the builder of a `contains not or at most` check within the fluent API of a + * sophisticated `contains` assertion for [CharSequence]. + * + * @param T The input type of the search. + * @param S The search behaviour which should be applied for the input of the search. + * + * @constructor Represents the builder of a `contains not or at most` check within the fluent API of a + * sophisticated `contains` assertion for [CharSequence]. + * @param times The number which the check will compare against the actual number of times an expected object is + * found in the input of the search. + * @param containsBuilder The previously used [CharSequenceContains.Builder]. + */ +internal class NotOrAtMostCheckerOptionImpl( + times: Int, + containsBuilder: CharSequenceContains.Builder +) : NotOrAtMostCheckerOptionBase( + times, + containsBuilder, + nameContainsNotValuesFun(), + { "`${containsBuilder::notOrAtMost.name} $it`" } +), NotOrAtMostCheckerOption diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/nameContainsNotFun.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/nameContainsNotFun.kt new file mode 100644 index 000000000..d5757867f --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/creating/charsequence/contains.builders/impl/nameContainsNotFun.kt @@ -0,0 +1,11 @@ +package ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.impl + +import ch.tutteli.atrium.api.infix.en_GB.Values +import ch.tutteli.atrium.api.infix.en_GB.containsNot +import ch.tutteli.atrium.creating.Expect +import kotlin.reflect.KFunction2 + +internal fun nameContainsNotValuesFun(): String { + val f: KFunction2, Values, Expect> = Expect::containsNot + return "`${f.name} ${Values::class.simpleName}`" +} diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/featureAssertions.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/featureAssertions.kt index dfe813e4d..4b3c61686 100644 --- a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/featureAssertions.kt +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/featureAssertions.kt @@ -20,7 +20,7 @@ import kotlin.reflect.* * * @since 0.10.0 */ -infix fun Expect.feature(property: KProperty1): FeatureExpect = +infix fun Expect.feature(property: KProperty1): FeatureExpect = ExpectImpl.feature.property(this, property).getExpectOfFeature() /** @@ -47,6 +47,8 @@ infix fun Expect.feature(f: KFunction1): FeatureExpect = * Use `of(K..., ...)` to create a [Feature] where the first argument is the extractor in form of a * [KProperty1] or a `KFunctionX` and potentially the required arguments for a `KFunctionX` where `X` > 1. * + * Note, [Feature] will be made invariant once Kotlin 1.4 is out and Atrium depends on it (most likely with 1.0.0) + * * @param of Use `of(K..., ...)` to create a [Feature] where the first argument is the extractor in form of a * [KProperty1] or a `KFunctionX` and potentially the required arguments for a `KFunctionX` where `X` > 1. * @@ -54,7 +56,8 @@ infix fun Expect.feature(f: KFunction1): FeatureExpect = * * @since 0.10.0 */ -infix fun Expect.feature(of: Feature): FeatureExpect = +//TODO remove `in` with Kotlin 1.4 (most likely with Atrium 1.0.0) +infix fun Expect.feature(of: Feature): FeatureExpect = ExpectImpl.feature.manualFeature(this, of.description, of.extractor).getExpectOfFeature() /** @@ -67,6 +70,8 @@ infix fun Expect.feature(of: Feature): FeatureExpect = * form of a [KProperty1] or a `KFunctionX`, the last an `assertionCreator`-lambda and the remaining arguments * in-between the required arguments in case of a `KFunctionX` where `X` > 1. * + * Note, [FeatureWithCreator] will be made invariant once Kotlin 1.4 is out and Atrium depends on it (most likely with 1.0.0) + * * @param of Use `of(K..., ...) { ... }` to create a [FeatureWithCreator] where the first argument is the extractor in * form of a [KProperty1] or a `KFunctionX`, the last an `assertionCreator`-lambda and the remaining arguments * in-between the required arguments in case of a `KFunctionX` where `X` > 1. @@ -76,7 +81,8 @@ infix fun Expect.feature(of: Feature): FeatureExpect = * * @since 0.10.0 */ -infix fun Expect.feature(of: FeatureWithCreator): Expect = +//TODO remove `in` with Kotlin 1.4 (most likely with Atrium 1.0.0) +infix fun Expect.feature(of: FeatureWithCreator): Expect = ExpectImpl.feature.manualFeature(this, of.description, of.extractor).addToInitial(of.assertionCreator) @@ -182,7 +188,7 @@ fun of(f: KFunction6, a1: A /** * Helper function to create a [FeatureWithCreator] based on a [KProperty1] + [assertionCreator]. */ -fun of(property: KProperty1, assertionCreator: Expect.() -> Unit): FeatureWithCreator = +fun of(property: KProperty1, assertionCreator: Expect.() -> Unit): FeatureWithCreator = FeatureWithCreator(property.name, { property.invoke(it) }, assertionCreator) /** diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/keywords.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/keywords.kt index e6b45b239..16647ec91 100644 --- a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/keywords.kt +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/keywords.kt @@ -2,6 +2,8 @@ package ch.tutteli.atrium.api.infix.en_GB +import ch.tutteli.atrium.creating.Expect + /** * Marker interface for keywords. * @@ -10,6 +12,7 @@ package ch.tutteli.atrium.api.infix.en_GB */ interface Keyword +//TODO not used yet, should be used though internal const val ERR_KEYWORD_GIVEN_COLLECTION_ASSUMED = "This call will most probably fail at runtime because the given subject is not a collection as you might have assumed. If you really want to compare the subject against the keyword, then cast the keyword to Any" @@ -49,6 +52,14 @@ object entries : Keyword */ object group : Keyword +/** + * Represents the pseudo keyword `not` as in [contains] `not`. + * It can be used for a parameter less function so that it has one parameter and thus can be used as infix function. + * + * @since 0.10.0 + */ +object not : Keyword + /** * Represents a filler, a pseudo keyword where there isn't really a good keyword. * A reader should skip this filler without reading it. For instance, `contains o atLeast 1...` should be read as @@ -60,6 +71,16 @@ object group : Keyword */ object o : Keyword +/** + * Workaround for https://youtrack.jetbrains.com/issue/KT-36624, extension property takes precedence over object. + * + * In case you want to refer to the pseudo-keyword `o` inside an [Expect] context, + * then you can use this type alias instead to circumvent the bug. + * + * @since 0.10.0 + */ +typealias O = o + /** * Represents the pseudo keyword `only` as in [and] `only`. * It can be used for a parameter less function so that it has one parameter and thus can be used as infix function. diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/parameterObjects.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/parameterObjects.kt index aeb3f4cde..7aff937a5 100644 --- a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/parameterObjects.kt +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/parameterObjects.kt @@ -1,6 +1,10 @@ +@file:Suppress("DEPRECATION" /** TODO remove suppress with 1.0.0 */) package ch.tutteli.atrium.api.infix.en_GB import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.domain.builders.utils.Group +import ch.tutteli.atrium.domain.builders.utils.GroupWithNullableEntries +import ch.tutteli.atrium.domain.builders.utils.GroupWithoutNullableEntries import ch.tutteli.atrium.domain.builders.utils.VarArgHelper /** @@ -25,3 +29,24 @@ class Pairs( override val expected: Pair, override vararg val otherExpected: Pair ) : VarArgHelper> + +/** + * Parameter object to express `String, vararg String` in the infix-api. + */ +class RegexPatterns(pattern: String, vararg otherPatterns: String) : VarArgHelper { + override val expected = pattern + override val otherExpected = otherPatterns +} + +/** + * Represents a [Group] of multiple values. + * + * Note, [Values] will be made invariant once Kotlin 1.4 is out and Atrium depends on it (most likely with 1.0.0) + */ +//TODO remove `out` with Kotlin 1.4 (most likely with Atrium 1.0.0) +class Values( + override val expected: T, + override vararg val otherExpected: T +) : GroupWithoutNullableEntries, GroupWithNullableEntries, VarArgHelper { + override fun toList() = listOf(expected, *otherExpected) +} diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/throwableAssertions.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/throwableAssertions.kt new file mode 100644 index 000000000..e5e12e58b --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/main/kotlin/ch/tutteli/atrium/api/infix/en_GB/throwableAssertions.kt @@ -0,0 +1,50 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.creating.Expect + +/** + * Expects that the property [Throwable.message] of the subject of the assertion is not null, + * creates an [Expect] for it and returns it. + * + * @return The newly created [Expect] for the property [Throwable.message] of the subject of the assertion. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +val Expect.message: Expect + get() = o feature Throwable::message notToBeNull O + +/** + * Expects that the property [Throwable.message] of the subject of the assertion is not null and + * holds all assertions the given [assertionCreator] creates for it and + * returns an [Expect] for the current subject of the assertion. + * + * @return An [Expect] for the current subject of the assertion. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.message(assertionCreator: Expect.() -> Unit): Expect = + o feature of(Throwable::message) { o notToBeNull assertionCreator } + +/** + * Expects that the property [Throwable.message] of the subject of the assertion is not null and contains + * [expected]'s [toString] representation using a non disjoint search. + ** + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed + * (this function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * @return An [Expect] for the current subject of the assertion. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.messageContains(expected: Any): Expect = + this messageContains Values(expected) + +/** + * Expects that the property [Throwable.message] of the subject of the assertion is not null and contains + * [values]'s [toString] representation using a non disjoint search. + ** + * Notice that a runtime check applies which assures that only [CharSequence], [Number] and [Char] are passed + * (this function expects `Any` for your convenience, so that you can mix [String] and [Int] for instance). + * + * @return An [Expect] for the current subject of the assertion. + * @throws AssertionError Might throw an [AssertionError] if the assertion made is not correct. + */ +infix fun Expect.messageContains(values: Values): Expect = + message { contains(values) } diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceAssertionsSpec.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceAssertionsSpec.kt new file mode 100644 index 000000000..59a7deb8f --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceAssertionsSpec.kt @@ -0,0 +1,50 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.specs.fun1 +import ch.tutteli.atrium.specs.notImplemented +import ch.tutteli.atrium.specs.testutils.WithAsciiReporter + +class CharSequenceAssertionsSpec : ch.tutteli.atrium.specs.integration.CharSequenceAssertionsSpec( + "toBe ${Empty::class.simpleName}" to ::toBeEmpty, + "notToBe ${Empty::class.simpleName}" to ::notToBeEmpty, + "notToBe ${Blank::class.simpleName}" to ::notToBeBlank, + fun1(Expect::startsWith), + fun1(Expect::startsWith), + fun1(Expect::startsNotWith), + fun1(Expect::startsNotWith), + fun1(Expect::endsWith), + fun1(Expect::endsWith), + fun1(Expect::endsNotWith), + fun1(Expect::endsNotWith), + fun1(Expect::matches), + fun1(Expect::mismatches) +) { + companion object : WithAsciiReporter() + + @Suppress("unused", "UNUSED_VALUE") + private fun ambiguityTest() { + val a1: Expect = notImplemented() + + a1 toBe Empty + a1 notToBe Empty + a1 notToBe Blank + + a1 startsWith "expected" + a1 startsNotWith "expected" + a1 endsWith "expected" + a1 endsNotWith "expected" + + a1 startsWith 'a' + a1 startsNotWith 'a' + a1 endsWith 'a' + a1 endsNotWith 'a' + + a1 matches Regex("a") + a1 mismatches Regex("a") + } +} + +private fun toBeEmpty(expect: Expect) = expect toBe Empty +private fun notToBeEmpty(expect: Expect) = expect notToBe Empty +private fun notToBeBlank(expect: Expect) = expect notToBe Blank diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsAtLeastAssertionsSpec.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsAtLeastAssertionsSpec.kt new file mode 100644 index 000000000..47cbe2149 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsAtLeastAssertionsSpec.kt @@ -0,0 +1,174 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.api.verbs.internal.expect +import ch.tutteli.atrium.creating.Expect +import org.spekframework.spek2.Spek +import org.spekframework.spek2.style.specification.describe + +class CharSequenceContainsAtLeastAssertionsSpec : Spek({ + include(object : ch.tutteli.atrium.specs.integration.CharSequenceContainsAtLeastAssertionsSpec( + getAtLeastValuesTriple(), + getAtLeastIgnoringCaseValuesTriple(), + getAtLeastButAtMostValuesTriple(), + getAtLeastBustAtMostIgnoringCaseValuesTriple(), + getContainsNotPair(), + getExactlyPair(), + CharSequenceContainsAtLeastAssertionsSpec.Companion::getErrorMsgAtLeastButAtMost, + "* ", "- " + ) {}) + + include(object : ch.tutteli.atrium.specs.integration.CharSequenceContainsAtLeastAssertionsSpec( + getAtLeastElementsOfTriple(), + getAtLeastIgnoringCaseElementsOfTriple(), + getAtLeastButAtMostElementsOfTriple(), + getAtLeastButAtMostIgnoringCaseElementsOfTriple(), + getContainsNotPair(), + getExactlyPair(), + CharSequenceContainsAtLeastAssertionsSpec.Companion::getErrorMsgAtLeastButAtMost, + "* ", "- " + ) {}) + + include(object : Spek({ + describe("elementsOf") { + it("passing an empty iterable throws an IllegalArgumentException") { + expect { + expect("test") contains o atLeast 1 elementsOf emptyList() + }.toThrow { o messageContains "Iterable without elements are not allowed" } + } + } + describe("elementsOf ignoring case") { + it("passing an empty iterable throws an IllegalArgumentException") { + expect { + expect("test") contains o ignoring case atLeast 1 elementsOf emptyList() + }.toThrow { o messageContains "Iterable without elements are not allowed" } + } + } + }) {}) +}) { + + companion object : CharSequenceContainsSpecBase() { + + private val atLeastDescr = { what: String, times: String -> "$contains $what $atLeast $times" } + internal fun getAtLeastValuesTriple() = + atLeastDescr to ("$contains o $atLeast" to Companion::containsAtLeast) + + private fun containsAtLeast( + expect: Expect, + atLeast: Int, + a: Any, + aX: Array + ): Expect = + if (aX.isEmpty()) expect contains o atLeast atLeast value a + else expect contains o atLeast atLeast the Values(a, *aX) + + internal fun getAtLeastElementsOfTriple() = + atLeastDescr to ("$contains o $atLeast" to Companion::containsAtLeastElementsOf) + + private fun containsAtLeastElementsOf( + expect: Expect, + atLeast: Int, + a: Any, + aX: Array + ): Expect = + expect contains o atLeast atLeast elementsOf listOf(a, *aX) + + private val atLeastIgnoringCaseDescr = + { what: String, times: String -> "$contains o $ignoringCase $what $atLeast $times" } + + private fun getAtLeastIgnoringCaseValuesTriple() = + atLeastIgnoringCaseDescr to ("$contains o $ignoringCase $atLeast" to Companion::containsAtLeastIgnoringCaseValues) + + private fun containsAtLeastIgnoringCaseValues( + expect: Expect, + atLeast: Int, + a: Any, + aX: Array + ): Expect = + if (aX.isEmpty()) { + if (atLeast == 1) expect contains o ignoring case value a + else expect contains o ignoring case atLeast atLeast value a + } else { + if (atLeast == 1) expect contains o ignoring case the Values(a, *aX) + else expect contains o ignoring case atLeast atLeast the Values(a, *aX) + } + + private fun getAtLeastIgnoringCaseElementsOfTriple() = + atLeastIgnoringCaseDescr to ("$contains o $ignoringCase $atLeast" to Companion::containsAtLeastIgnoringCaseElementsOf) + + private fun containsAtLeastIgnoringCaseElementsOf( + expect: Expect, + atLeast: Int, + a: Any, + aX: Array + ): Expect = + expect contains o ignoring case atLeast atLeast elementsOf listOf(a, *aX) + + private val atLeastButAtMostDescr = { what: String, timesAtLeast: String, timesAtMost: String -> + "$contains o $what $atLeast $timesAtLeast $butAtMost $timesAtMost" + } + + private fun getAtLeastButAtMostElementsOfTriple() = + atLeastButAtMostDescr to ("$contains o $atLeast o $butAtMost" to Companion::containsAtLeastButAtMostElementsOf) + + private fun containsAtLeastButAtMostElementsOf( + expect: Expect, + atLeast: Int, + butAtMost: Int, + a: Any, + aX: Array + ) = expect contains o atLeast atLeast butAtMost butAtMost elementsOf listOf(a, *aX) + + private fun getAtLeastButAtMostValuesTriple() = + atLeastButAtMostDescr to ("$contains o $atLeast o $butAtMost" to Companion::containsAtLeastButAtMostValues) + + private fun containsAtLeastButAtMostValues( + expect: Expect, + atLeast: Int, + butAtMost: Int, + a: Any, + aX: Array + ) = + if (aX.isEmpty()) expect contains o atLeast atLeast butAtMost butAtMost value a + else expect contains o atLeast atLeast butAtMost butAtMost the Values(a, *aX) + + private val atLeastButAtMostIgnoringCaseDescr = { what: String, timesAtLeast: String, timesAtMost: String -> + "$contains $ignoringCase $what $atLeast $timesAtLeast $butAtMost $timesAtMost" + } + + private fun getAtLeastBustAtMostIgnoringCaseValuesTriple() = + atLeastButAtMostIgnoringCaseDescr to ("$contains o $ignoringCase $atLeast $butAtMost" to Companion::containsAtLeastButAtMostIgnoringCaseValues) + + private fun containsAtLeastButAtMostIgnoringCaseValues( + expect: Expect, + atLeast: Int, + butAtMost: Int, + a: Any, + aX: Array + ) = + if (aX.isEmpty()) expect contains o ignoring case atLeast atLeast butAtMost butAtMost value a + else expect contains o ignoring case atLeast atLeast butAtMost butAtMost the Values(a, *aX) + + private fun getAtLeastButAtMostIgnoringCaseElementsOfTriple() = + atLeastButAtMostIgnoringCaseDescr to ("$contains o $ignoringCase $atLeast $butAtMost" to Companion::containsAtLeastButAtMostIgnoringCaseElementsOf) + + private fun containsAtLeastButAtMostIgnoringCaseElementsOf( + expect: Expect, + atLeast: Int, + butAtMost: Int, + a: Any, + aX: Array + ) = expect contains o ignoring case atLeast atLeast butAtMost butAtMost elementsOf listOf(a, *aX) + + private fun getContainsNotPair() = containsNotValues to Companion::getErrorMsgContainsNot + + private fun getErrorMsgContainsNot(times: Int) = "use `$containsNotValues` instead of `$atLeast $times`" + + private fun getExactlyPair() = exactly to Companion::getErrorMsgExactly + + private fun getErrorMsgExactly(times: Int) = + "use `$exactly $times` instead of `$atLeast $times $butAtMost $times`" + + internal fun getErrorMsgAtLeastButAtMost(timesAtLeast: Int, timesButAtMost: Int) = + "specifying `$butAtMost $timesButAtMost` does not make sense if `$atLeast $timesAtLeast` was used before" + } +} diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsAtMostAssertionsSpec.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsAtMostAssertionsSpec.kt new file mode 100644 index 000000000..ad05a8949 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsAtMostAssertionsSpec.kt @@ -0,0 +1,42 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.creating.Expect + + +class CharSequenceContainsAtMostAssertionsSpec : + ch.tutteli.atrium.specs.integration.CharSequenceContainsAtMostAssertionsSpec( + getAtMostTriple(), + getAtMostIgnoringCaseTriple(), + getContainsNotPair(), + getExactlyPair(), + "* ", "- " + ) { + + companion object : CharSequenceContainsSpecBase() { + + private fun getAtMostTriple() = + { what: String, times: String -> "$contains $what $atMost $times" } to + ("$contains o $atMost" to Companion::containsAtMost) + + private fun containsAtMost(expect: Expect, atMost: Int, a: Any, aX: Array) = + if (aX.isEmpty()) expect contains o atMost atMost value a + else expect contains o atMost atMost the Values(a, *aX) + + private fun getAtMostIgnoringCaseTriple() = + { what: String, times: String -> "$contains $ignoringCase $what $atMost $times" } to + ("$contains o $ignoringCase $atMost" to Companion::containsAtMostIgnoringCase) + + private fun containsAtMostIgnoringCase(expect: Expect, atMost: Int, a: Any, aX: Array) = + if (aX.isEmpty()) expect contains o ignoring case atMost atMost value a + else expect contains o ignoring case atMost atMost the Values(a, *aX) + + private fun getContainsNotPair() = containsNotValues to Companion::getErrorMsgContainsNot + + private fun getErrorMsgContainsNot(times: Int) = "use `$containsNotValues` instead of `$atMost $times`" + + private fun getExactlyPair() = exactly to Companion::getErrorMsgExactly + + private fun getErrorMsgExactly(times: Int) = + "use `$exactly $times` instead of `$atMost $times`; `$atMost $times` defines implicitly `$atLeast $times` as well" + } +} diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsContainsNotAssertionsSpec.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsContainsNotAssertionsSpec.kt new file mode 100644 index 000000000..0f195dc14 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsContainsNotAssertionsSpec.kt @@ -0,0 +1,31 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.specs.fun2 +import ch.tutteli.atrium.specs.notImplemented +import ch.tutteli.atrium.specs.testutils.WithAsciiReporter + +class CharSequenceContainsContainsNotAssertionsSpec : + ch.tutteli.atrium.specs.integration.CharSequenceContainsContainsNotAssertionsSpec( + fun2>(::contains), + fun2>(::containsNot), + "* ", "- ", ">> " + ) { + companion object : WithAsciiReporter() + + @Suppress("unused", "UNUSED_VALUE") + private fun ambiguityTest() { + val a1: Expect = notImplemented() + + a1 contains Values(1, "a", 'c') + a1 containsNot Values(1, "a", 'c') + } +} + +private fun contains(expect: Expect, a: Any, aX: Array) = + if (aX.isEmpty()) expect contains a + else expect contains Values(a, *aX) + +private fun containsNot(expect: Expect, a: Any, aX: Array) = + if (aX.isEmpty()) expect containsNot a + else expect containsNot Values(a, *aX) diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsExactlyAssertionsSpec.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsExactlyAssertionsSpec.kt new file mode 100644 index 000000000..0cdcd4ae7 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsExactlyAssertionsSpec.kt @@ -0,0 +1,40 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.creating.Expect + +class CharSequenceContainsExactlyAssertionsSpec : + ch.tutteli.atrium.specs.integration.CharSequenceContainsExactlyAssertionsSpec( + getExactlyTriple(), + getExactlyIgnoringCaseTriple(), + getContainsNotPair(), + "* ", "- " + ) { + + companion object : CharSequenceContainsSpecBase() { + + private fun getExactlyTriple() = + { what: String, times: String -> "$contains $what $exactly $times" } to + ("$contains o $exactly" to Companion::containsExactly) + + private fun containsExactly(expect: Expect, exactly: Int, a: Any, aX: Array) = + if (aX.isEmpty()) expect contains o exactly exactly value a + else expect contains o exactly exactly the Values(a, *aX) + + private fun getExactlyIgnoringCaseTriple() = + { what: String, times: String -> "$contains $ignoringCase $what $exactly $times" } to + ("$contains o $ignoringCase $exactly" to Companion::containsExactlyIgnoringCase) + + + private fun containsExactlyIgnoringCase( + expect: Expect, + exactly: Int, + a: Any, + aX: Array + ) = + if (aX.isEmpty()) expect contains o ignoring case exactly exactly value a + else expect contains o ignoring case exactly exactly the Values(a, *aX) + + private fun getContainsNotPair() = containsNotValues to Companion::getErrorMsgContainsNot + private fun getErrorMsgContainsNot(times: Int) = "use `$containsNotValues` instead of `$exactly $times`" + } +} diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsNotAssertionsSpec.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsNotAssertionsSpec.kt new file mode 100644 index 000000000..6a83923e7 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsNotAssertionsSpec.kt @@ -0,0 +1,29 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.creating.Expect + +class CharSequenceContainsNotAssertionsSpec : ch.tutteli.atrium.specs.integration.CharSequenceContainsNotAssertionsSpec( + getContainsNotTriple(), + getContainsNotIgnoringCaseTriple(), + "* ", "- " +) { + + companion object : CharSequenceContainsSpecBase() { + + private fun getContainsNotTriple() = + { what: String -> "$containsNotValues $what" } to + (containsNotValues to Companion::containsNotFun) + + private fun containsNotFun(expect: Expect, a: Any, aX: Array) = + if (aX.isEmpty()) expect contains not value a + else expect contains not the Values(a, *aX) + + private fun getContainsNotIgnoringCaseTriple() = + { what: String -> "$containsNotValues $ignoringCase $what" } to + ("$containsNotValues o $ignoringCase" to Companion::containsNotIgnoringCase) + + private fun containsNotIgnoringCase(expect: Expect, a: Any, aX: Array) = + if (aX.isEmpty()) expect contains not ignoring case value a + else expect contains not ignoring case the Values(a, *aX) + } +} diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsNotOrAtMostAssertionsSpec.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsNotOrAtMostAssertionsSpec.kt new file mode 100644 index 000000000..954ba37cc --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsNotOrAtMostAssertionsSpec.kt @@ -0,0 +1,42 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.creating.Expect + +class CharSequenceContainsNotOrAtMostAssertionsSpec : + ch.tutteli.atrium.specs.integration.CharSequenceContainsNotOrAtMostAssertionsSpec( + getNotOrAtMostTriple(), + getNotOrAtMostIgnoringCaseTriple(), + getContainsNotPair(), + "* ", "- " + ) { + + companion object : CharSequenceContainsSpecBase() { + + private fun getNotOrAtMostTriple() = + { what: String, times: String -> "$contains $what $notOrAtMost $times" } to + ("$contains o $notOrAtMost" to Companion::containsNotOrAtMost) + + private fun containsNotOrAtMost(expect: Expect, atMost: Int, a: Any, aX: Array) = + if (aX.isEmpty()) expect contains o notOrAtMost atMost value a + else expect contains o notOrAtMost atMost the Values(a, *aX) + + private fun getNotOrAtMostIgnoringCaseTriple() = + { what: String, times: String -> "$contains $ignoringCase $what $notOrAtMost $times" } to + ("$contains o $ignoringCase $notOrAtMost" to Companion::containsNotOrAtMostIgnoringCase) + + private fun containsNotOrAtMostIgnoringCase( + expect: Expect, + atMost: Int, + a: Any, + aX: Array + ) = + if (aX.isEmpty()) expect contains o ignoring case notOrAtMost atMost value a + else expect contains o ignoring case notOrAtMost atMost the Values(a, *aX) + + + private fun getContainsNotPair() = containsNotValues to Companion::getErrorMsgContainsNot + + private fun getErrorMsgContainsNot(times: Int) = "use `$containsNotValues` instead of `$notOrAtMost $times`" + + } +} diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsRegexAssertionsSpec.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsRegexAssertionsSpec.kt new file mode 100644 index 000000000..3b4d48db5 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsRegexAssertionsSpec.kt @@ -0,0 +1,139 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.creating.Expect +import org.spekframework.spek2.Spek + +class CharSequenceContainsRegexAssertionsSpec : Spek({ + include(StringSpec) + include(RegexSpec) +}) { + object StringSpec : ch.tutteli.atrium.specs.integration.CharSequenceContainsRegexAssertionsSpec( + getNameContainsRegex(), + getAtLeastTripleString(), + getAtLeastIgnoringCaseTripleString(), + getShortcutTripleString(), + getAtMostTripleString(), + getAtMostIgnoringCaseTripleString(), + "* ", "- ", + "[StringSpec] " + ) + + object RegexSpec : ch.tutteli.atrium.specs.integration.CharSequenceContainsRegexAssertionsSpec( + getNameContainsRegex(), + getAtLeastTripleRegex(), + getAtLeastIgnoringCaseTripleString(), + getShortcutTripleRegex(), + getAtMostTripleRegex(), + getAtMostIgnoringCaseTripleString(), + "* ", "- ", + "[RegexSpec] " + ) + + companion object : CharSequenceContainsSpecBase() { + + private fun getNameContainsRegex() = "$contains with search mode $regex" + + private fun getAtLeastTripleString() = + { what: String, times: String -> "$contains $what $atLeast $times" } to + ("$contains o $atLeast $regex" to ::containsAtLeastString) + + private fun getAtLeastTripleRegex() = + { what: String, times: String -> "$contains $what $atLeast $times" } to + ("$contains o $atLeast $regex" to ::containsAtLeastRegex) + + private fun containsAtLeastString( + expect: Expect, + atLeast: Int, + a: String, + aX: Array + ) = + if (aX.isEmpty()) expect contains o atLeast atLeast regex a + else expect contains o atLeast atLeast the RegexPatterns(a, *aX) + + private fun containsAtLeastRegex(expect: Expect, atLeast: Int, a: String, aX: Array) = + if (aX.isEmpty()) expect contains o atLeast atLeast matchFor Regex(a) + else expect contains o atLeast atLeast matchFor All(Regex(a), *aX.map { it.toRegex() }.toTypedArray()) + + private fun getAtLeastIgnoringCaseTripleString() = + { what: String, times: String -> "$contains $ignoringCase $what $atLeast $times" } to + ("$contains o $atLeast $ignoringCase $regex" to ::containsAtLeastIgnoringCase) + + private fun containsAtLeastIgnoringCase( + expect: Expect, + atLeast: Int, + a: String, + aX: Array + ) = + if (aX.isEmpty()) { + if (atLeast == 1) expect contains o ignoring case regex a + else expect contains o ignoring case atLeast atLeast regex a + } else { + if (atLeast == 1) expect contains o ignoring case the RegexPatterns(a, *aX) + else expect contains o ignoring case atLeast atLeast the RegexPatterns(a, *aX) + } + + private fun getShortcutTripleString() = + { what: String, times: String -> "$contains $what $atLeast $times" } to + (containsRegex to ::containsShortcutString) + + private fun getShortcutTripleRegex() = + { what: String, times: String -> "$contains $what $atLeast $times" } to + (containsRegex to ::containsShortcutRegex) + + private fun containsShortcutString( + expect: Expect, + a: String, + aX: Array + ) = + if (aX.isEmpty()) expect containsRegex a + else expect containsRegex RegexPatterns(a, *aX) + + private fun containsShortcutRegex( + expect: Expect, + a: String, + aX: Array + ) = + if (aX.isEmpty()) expect contains Regex(a) + else expect contains All(Regex(a), *aX.map { it.toRegex() }.toTypedArray()) + + + private fun getAtMostTripleString() = + { what: String, times: String -> "$contains $what $atMost $times" } to + ("$contains o $atMost $regex" to ::containsAtMostString) + + private fun getAtMostTripleRegex() = + { what: String, times: String -> "$contains $what $atMost $times" } to + ("$contains o $atMost $regex" to ::containsAtMostRegex) + + private fun containsAtMostString( + expect: Expect, + atMost: Int, + a: String, + aX: Array + ) = + if (aX.isEmpty()) expect contains o atMost atMost regex a + else expect contains o atMost atMost the RegexPatterns(a, *aX) + + private fun getAtMostIgnoringCaseTripleString() = + { what: String, times: String -> "$contains $ignoringCase $what $atMost $times" } to + ("$contains o $ignoringCase $atMost $regex" to ::containsAtMostIgnoringCase) + + private fun containsAtMostRegex( + expect: Expect, + atMost: Int, + a: String, + aX: Array + ) = + if (aX.isEmpty()) expect contains o atMost atMost matchFor Regex(a) + else expect contains o atMost atMost matchFor All(Regex(a), *aX.map { it.toRegex() }.toTypedArray()) + + private fun containsAtMostIgnoringCase( + expect: Expect, + atMost: Int, + a: String, + aX: Array + ) = + if (aX.isEmpty()) expect contains o ignoring case atMost atMost regex a + else expect contains o ignoring case atMost atMost the RegexPatterns(a, *aX) + } +} diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsSpecBase.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsSpecBase.kt new file mode 100644 index 000000000..b28852362 --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/CharSequenceContainsSpecBase.kt @@ -0,0 +1,60 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.api.infix.en_GB.creating.charsequence.contains.builders.AtLeastCheckerOption +import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.domain.creating.charsequence.contains.CharSequenceContains +import ch.tutteli.atrium.domain.creating.charsequence.contains.searchbehaviours.NoOpSearchBehaviour +import ch.tutteli.atrium.specs.fun1 +import ch.tutteli.atrium.specs.name +import ch.tutteli.atrium.specs.notImplemented +import ch.tutteli.atrium.specs.testutils.WithAsciiReporter +import kotlin.reflect.KFunction2 + +abstract class CharSequenceContainsSpecBase : WithAsciiReporter() { + private val containsProp: KFunction2, o, CharSequenceContains.Builder> = + Expect::contains + protected val contains = containsProp.name + private val containsNotFun: KFunction2, Any, Expect> = Expect::containsNot + protected val containsNotValues = "${containsNotFun.name} ${Values::class.simpleName}" + protected val containsRegex = fun1(Expect::containsRegex).name + protected val atLeast = CharSequenceContains.Builder<*, *>::atLeast.name + protected val butAtMost = AtLeastCheckerOption<*, *>::butAtMost.name + protected val exactly = CharSequenceContains.Builder<*, *>::exactly.name + protected val atMost = CharSequenceContains.Builder<*, *>::atMost.name + protected val notOrAtMost = CharSequenceContains.Builder<*, *>::notOrAtMost.name + private val regexKFun: KFunction2< + CharSequenceContains.CheckerOption<*, NoOpSearchBehaviour>, + String, + Expect<*> + > = CharSequenceContains.CheckerOption<*, NoOpSearchBehaviour>::regex + protected val regex = regexKFun.name + protected val ignoringCase = + "${CharSequenceContains.Builder<*, NoOpSearchBehaviour>::ignoring.name} ${case::class.simpleName}" + + @Suppress("unused", "UNUSED_VALUE") + private fun ambiguityTest() { + val a1: Expect = notImplemented() + + a1 contains o atLeast 1 value 1 + a1 contains o atMost 2 the Values("a", 1) + a1 contains o notOrAtMost 2 regex "h|b" + a1 contains o exactly 2 the RegexPatterns("h|b", "b") + a1 contains o atLeast 2 matchFor Regex("bla") + a1 contains o atLeast 2 matchFor All(Regex("bla"), Regex("b")) + a1 contains o atLeast 2 elementsOf listOf(1, 2) + + a1 contains o ignoring case atLeast 1 value "a" + a1 contains o ignoring case atLeast 1 the Values("a", 'b') + a1 contains o ignoring case atLeast 1 regex "a" + a1 contains o ignoring case atLeast 1 the RegexPatterns("a", "bl") + a1 contains o ignoring case atLeast 1 elementsOf listOf(1, 2) + + // skip atLeast + a1 contains o ignoring case value "a" + a1 contains o ignoring case the Values("a", 'b') + a1 contains o ignoring case regex "a" + a1 contains o ignoring case the RegexPatterns("a", "bl") + //TODO add to infix as well as fluent + //a1 contains o ignoring case elementsOf listOf(1, 2) + } +} diff --git a/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/ThrowableAssertionsSpec.kt b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/ThrowableAssertionsSpec.kt new file mode 100644 index 000000000..825c4a4cf --- /dev/null +++ b/apis/infix-en_GB/atrium-api-infix-en_GB-common/src/test/kotlin/ch/tutteli/atrium/api/infix/en_GB/ThrowableAssertionsSpec.kt @@ -0,0 +1,29 @@ +package ch.tutteli.atrium.api.infix.en_GB + +import ch.tutteli.atrium.creating.Expect +import ch.tutteli.atrium.specs.fun1 +import ch.tutteli.atrium.specs.fun2 +import ch.tutteli.atrium.specs.notImplemented +import ch.tutteli.atrium.specs.property + +class ThrowableAssertionsSpec : ch.tutteli.atrium.specs.integration.ThrowableAssertionsSpec( + property(Expect::message), + fun1.() -> Unit>(Expect::message), + fun2(::messageContains) +) { + + @Suppress("unused", "UNUSED_VALUE") + private fun ambiguityTest() { + var a1: Expect = notImplemented() + + a1.message + a1 = a1 message {} + a1 = a1 messageContains "a" + a1 = a1 messageContains 'a' + a1 = a1 messageContains Values("a", 1, 'b') + } +} + +private fun messageContains(expect: Expect, expected: Any, vararg otherExpected: Any): Expect = + if (otherExpected.isEmpty()) expect messageContains expected + else expect messageContains Values(expected, *otherExpected) diff --git a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/utils/VarArgHelper.kt b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/utils/VarArgHelper.kt index 34f69587c..0c795e12c 100644 --- a/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/utils/VarArgHelper.kt +++ b/domain/builders/atrium-domain-builders-common/src/main/kotlin/ch/tutteli/atrium/domain/builders/utils/VarArgHelper.kt @@ -26,3 +26,14 @@ interface VarArgHelper { */ fun toList(): List = expected glue otherExpected } + +/** + * Transforms the given [iterable] to `Pair>` with the intend that it can be easily used for a function + * requiring `T, vararg T` + * + * @throws IllegalArgumentException in case the iterable is empty. + */ +inline fun toVarArg(iterable: Iterable): Pair> { + require(iterable.iterator().hasNext()) { "Iterable without elements are not allowed for this function." } + return iterable.first() to iterable.drop(1).toTypedArray() +} diff --git a/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/CharSequenceContainsRegexAssertionsSpec.kt b/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/CharSequenceContainsRegexAssertionsSpec.kt index aa00b115c..37273873f 100644 --- a/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/CharSequenceContainsRegexAssertionsSpec.kt +++ b/misc/specs/atrium-specs-common/src/main/kotlin/ch/tutteli/atrium/specs/integration/CharSequenceContainsRegexAssertionsSpec.kt @@ -66,55 +66,55 @@ abstract class CharSequenceContainsRegexAssertionsSpec( describeFun(containsRegex) { context("throws an ${IllegalArgumentException::class.simpleName}") { - it("if an erroneous pattern is passed to `$containsAtLeast` as first argument") { + it("if an erroneous pattern is passed to `${containsAtLeast.name}` as first argument") { expect { expect("a" as CharSequence).containsAtLeastFun(1, "notA(validPattern") }.toThrow() } - it("if an erroneous pattern is passed to `$containsAtLeast` as second argument") { + it("if an erroneous pattern is passed to `${containsAtLeast.name}` as second argument") { expect { expect("a" as CharSequence).containsAtLeastFun(1, "h(a|e)llo", "notA(validPattern") }.toThrow() } - it("if an erroneous pattern is passed to `$containsAtLeastIgnoringCase` as first argument") { + it("if an erroneous pattern is passed to `${containsAtLeastIgnoringCase.name}` as first argument") { expect { expect("a" as CharSequence).containsAtLeastIgnoringCaseFun(1, "notA(validPattern") }.toThrow() } - it("if an erroneous pattern is passed to `$containsAtLeastIgnoringCase` as second argument") { + it("if an erroneous pattern is passed to `${containsAtLeastIgnoringCase.name}` as second argument") { expect { expect("a" as CharSequence).containsAtLeastIgnoringCaseFun(1, "h(a|e)llo", "notA(validPattern") }.toThrow() } - it("if an erroneous pattern is passed to `$containsShortcut` as first argument") { + it("if an erroneous pattern is passed to `${containsShortcut.name}` as first argument") { expect { expect("a" as CharSequence).containsShortcutFun("notA(validPattern") }.toThrow() } - it("if an erroneous pattern is passed to `$containsShortcut` as second argument") { + it("if an erroneous pattern is passed to `${containsShortcut.name}` as second argument") { expect { expect("a" as CharSequence).containsShortcutFun("h(a|e)llo", "notA(validPattern") }.toThrow() } - it("if an erroneous pattern is passed to `$containsAtMost` as first argument") { + it("if an erroneous pattern is passed to `${containsAtMost.name}` as first argument") { expect { expect("a" as CharSequence).containsAtMostFun(2, "notA(validPattern") }.toThrow() } - it("if an erroneous pattern is passed to `$containsAtMost` as second argument") { + it("if an erroneous pattern is passed to `${containsAtMost.name}` as second argument") { expect { expect("a" as CharSequence).containsAtMostFun(2, "h(a|e)llo", "notA(validPattern") }.toThrow() } - it("if an erroneous pattern is passed to `$containsAtMostIgnoringCase` as first argument") { + it("if an erroneous pattern is passed to `${containsAtMostIgnoringCase.name}` as first argument") { expect { expect("a" as CharSequence).containsAtMostIgnoringCaseFun(2, "notA(validPattern") }.toThrow() } - it("if an erroneous pattern is passed to `$containsAtMostIgnoringCase` as second argument") { + it("if an erroneous pattern is passed to `${containsAtMostIgnoringCase.name}` as second argument") { expect { expect("a" as CharSequence).containsAtMostIgnoringCaseFun(2, "h(a|e)llo", "notA(validPattern") }.toThrow()