├── .github └── workflows │ └── gradle-wrapper-validation.yml ├── .gitignore ├── .java-version ├── .travis.yml ├── LICENSE ├── README.md ├── b ├── build.gradle ├── docs ├── describe.md ├── dokka │ ├── hamkrest │ │ ├── com.natpryce.hamkrest.assertion │ │ │ ├── assert-that.html │ │ │ └── index.html │ │ ├── com.natpryce.hamkrest │ │ │ ├── -case-sensitivity │ │ │ │ ├── -case-insensitive │ │ │ │ │ └── index.html │ │ │ │ ├── -case-sensitive │ │ │ │ │ └── index.html │ │ │ │ └── index.html │ │ │ ├── -match-result │ │ │ │ ├── -match │ │ │ │ │ ├── index.html │ │ │ │ │ └── to-string.html │ │ │ │ ├── -mismatch │ │ │ │ │ ├── -mismatch.html │ │ │ │ │ ├── description.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── to-string.html │ │ │ │ └── index.html │ │ │ ├── -matcher │ │ │ │ ├── -companion │ │ │ │ │ ├── index.html │ │ │ │ │ └── invoke.html │ │ │ │ ├── -conjunction │ │ │ │ │ ├── -conjunction.html │ │ │ │ │ ├── description.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── invoke.html │ │ │ │ ├── -disjunction │ │ │ │ │ ├── -disjunction.html │ │ │ │ │ ├── description.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── invoke.html │ │ │ │ ├── -negation │ │ │ │ │ ├── -negation.html │ │ │ │ │ ├── description.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── invoke.html │ │ │ │ │ ├── negated-description.html │ │ │ │ │ └── not.html │ │ │ │ ├── -primitive │ │ │ │ │ ├── -primitive.html │ │ │ │ │ └── index.html │ │ │ │ ├── as-predicate.html │ │ │ │ ├── description.html │ │ │ │ ├── index.html │ │ │ │ ├── invoke.html │ │ │ │ ├── negated-description.html │ │ │ │ └── not.html │ │ │ ├── -self-describing │ │ │ │ ├── description.html │ │ │ │ └── index.html │ │ │ ├── -string-matcher │ │ │ │ ├── -companion │ │ │ │ │ ├── index.html │ │ │ │ │ └── invoke.html │ │ │ │ ├── -string-matcher.html │ │ │ │ └── index.html │ │ │ ├── -value-description │ │ │ │ ├── describe.html │ │ │ │ └── index.html │ │ │ ├── absent.html │ │ │ ├── all-elements.html │ │ │ ├── all-of.html │ │ │ ├── and.html │ │ │ ├── any-element.html │ │ │ ├── any-of.html │ │ │ ├── anything.html │ │ │ ├── case-insensitive.html │ │ │ ├── case-sensitive.html │ │ │ ├── cast.html │ │ │ ├── close-to.html │ │ │ ├── contains-substring.html │ │ │ ├── contains.html │ │ │ ├── describe.html │ │ │ ├── described-by.html │ │ │ ├── ends-with.html │ │ │ ├── equal-to-ignoring-case.html │ │ │ ├── equal-to.html │ │ │ ├── greater-than-or-equal-to.html │ │ │ ├── greater-than.html │ │ │ ├── has-element.html │ │ │ ├── has-size.html │ │ │ ├── has.html │ │ │ ├── identifier-to-words.html │ │ │ ├── index.html │ │ │ ├── is-a.html │ │ │ ├── is-blank.html │ │ │ ├── is-empty-string.html │ │ │ ├── is-empty.html │ │ │ ├── is-in.html │ │ │ ├── is-null-or-blank.html │ │ │ ├── is-null-or-empty-string.html │ │ │ ├── is-within.html │ │ │ ├── less-than-or-equal-to.html │ │ │ ├── less-than.html │ │ │ ├── matches.html │ │ │ ├── nothing.html │ │ │ ├── or.html │ │ │ ├── present.html │ │ │ ├── same-instance.html │ │ │ ├── starts-with.html │ │ │ └── throws.html │ │ └── package-list │ ├── images │ │ ├── anchor-copy-button.svg │ │ ├── arrow_down.svg │ │ ├── burger.svg │ │ ├── copy-icon.svg │ │ ├── copy-successful-icon.svg │ │ ├── footer-go-to-link.svg │ │ ├── go-to-top-icon.svg │ │ ├── homepage.svg │ │ ├── logo-icon.svg │ │ ├── nav-icons │ │ │ ├── abstract-class-kotlin.svg │ │ │ ├── abstract-class.svg │ │ │ ├── annotation-kotlin.svg │ │ │ ├── annotation.svg │ │ │ ├── class-kotlin.svg │ │ │ ├── class.svg │ │ │ ├── enum-kotlin.svg │ │ │ ├── enum.svg │ │ │ ├── exception-class.svg │ │ │ ├── field-value.svg │ │ │ ├── field-variable.svg │ │ │ ├── function.svg │ │ │ ├── interface-kotlin.svg │ │ │ ├── interface.svg │ │ │ ├── object.svg │ │ │ └── typealias-kotlin.svg │ │ └── theme-toggle.svg │ ├── index.html │ ├── navigation.html │ ├── scripts │ │ ├── clipboard.js │ │ ├── main.js │ │ ├── navigation-loader.js │ │ ├── pages.json │ │ ├── platform-content-handler.js │ │ ├── prism.js │ │ ├── sourceset_dependencies.js │ │ └── symbol-parameters-wrapper_deferred.js │ └── styles │ │ ├── font-jb-sans-auto.css │ │ ├── logo-styles.css │ │ ├── main.css │ │ ├── prism.css │ │ └── style.css ├── index.md └── matchers │ ├── equalTo.md │ └── index.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── publish-release ├── settings.gradle ├── src ├── main │ └── kotlin │ │ └── com │ │ └── natpryce │ │ └── hamkrest │ │ ├── SelfDescribing.kt │ │ ├── ValueDescription.kt │ │ ├── assertion │ │ └── assert.kt │ │ ├── collection_matchers.kt │ │ ├── core_matchers.kt │ │ ├── describe.kt │ │ ├── internal.kt │ │ ├── matching.kt │ │ ├── number_matchers.kt │ │ └── string_matchers.kt └── test │ ├── kotlin │ └── com │ │ └── natpryce │ │ └── hamkrest │ │ ├── assertion │ │ └── assert_tests.kt │ │ ├── assertions.kt │ │ ├── collection_matchers_tests.kt │ │ ├── core_matchers_tests.kt │ │ ├── describe_tests.kt │ │ ├── internal_tests.kt │ │ ├── matching_tests.kt │ │ ├── number_matchers.kt │ │ └── string_matchers_tests.kt │ └── resources │ └── META-INF │ └── services │ └── com.natpryce.hamkrest.ValueDescription └── tag-release /.github/workflows/gradle-wrapper-validation.yml: -------------------------------------------------------------------------------- 1 | name: "Validate Gradle Wrapper" 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | validation: 6 | name: "Validation" 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: gradle/wrapper-validation-action@v1 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | .gradle/ 4 | build/ 5 | out/ 6 | *.class 7 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 1.8 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | script: ./b clean build 4 | 5 | sudo: false 6 | 7 | jdk: 8 | - openjdk8 9 | - openjdk11 10 | 11 | cache: 12 | directories: 13 | - $HOME/.m2 14 | - $HOME/.gradle 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HamKrest - Hamcrest for Kotlin 2 | ============================== 3 | 4 | A reimplementation of Hamcrest to take advantage of [Kotlin](https://kotlinlang.org/) language features. 5 | 6 | [![Kotlin](https://img.shields.io/badge/kotlin-1.3.11-blue.svg)](http://kotlinlang.org) 7 | [![Build Status](https://travis-ci.org/npryce/hamkrest.svg?branch=master)](https://travis-ci.org/npryce/hamkrest) 8 | [![Maven Central](https://img.shields.io/maven-central/v/com.natpryce/hamkrest.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.natpryce%22%20AND%20a%3A%22hamkrest%22) 9 | 10 | Note: as of version 1.4.0.0, you must add kotlin-reflect to the classpath to use HamKrest's reflective features. 11 | 12 | 13 | When working in Kotlin, Hamkrest provides these benefits over using the Java Hamcrest library: 14 | 15 | * Kotlin's type system means that developers don't have to worry about getting the variance of generic signatures right. Variance is defined on the abstract Matcher type and Kotlin makes sure composition and subtyping work together the way you expect. 16 | 17 | * Syntactic sugar. You can negate a matcher with the ! operator and compose matchers with infix `and` and `or` functions: 18 | 19 | ``` kotlin 20 | import com.natpryce.hamkrest.assertion.assert 21 | 22 | ... 23 | 24 | assertThat("xyzzy", startsWith("x") and endsWith("y") and !containsSubstring("a")) 25 | ``` 26 | 27 | * Easier to extend. You can convert named unary predicates into matchers. 28 | 29 | ``` kotlin 30 | val isBlank = Matcher(String::isBlank) 31 | 32 | assertThat(input, isBlank) 33 | ``` 34 | 35 | As a shortcut, you can pass named functions to the `assertThat`, `and`, `or` and many other functions that take a matcher. 36 | 37 | ``` kotlin 38 | assertThat(input, String::isBlank) 39 | ``` 40 | 41 | You can also convert a named binary predicate and the second argument to a matcher for first argument, which works well for extension methods. 42 | 43 | ``` kotlin 44 | fun String.hasLength(n: Int): Boolean = this.length == n 45 | 46 | val isTheRightLength = Matcher(String::hasLength, 8) 47 | 48 | assertThat(secretCode, isTheRightLength) 49 | ``` 50 | 51 | You can use function and property references to match features of a value: 52 | 53 | ``` kotlin 54 | val isLongEnough = has(String::length, greaterThan(8)) 55 | 56 | assertThat(password, isLongEnough) 57 | ``` 58 | 59 | All of these shortcuts produce good, human-readable diagnostics. 60 | 61 | You can customise how diagnostics are generated by creating a project-specific `assert` object. 62 | 63 | 64 | ## More documentation 65 | 66 | [More detailed documentation of specific library features is in the docs/ directory](docs/). 67 | -------------------------------------------------------------------------------- /b: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | version=`git describe --always --tags --match '*.*.*.*' --dirty="_patched"` 4 | `dirname $0`/gradlew -P-version=${version} "$@" 5 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "org.jetbrains.kotlin.jvm" version "1.4.10" 3 | id 'maven' 4 | id 'signing' 5 | id("org.jetbrains.dokka") version "1.9.20" 6 | } 7 | 8 | group 'com.natpryce' 9 | 10 | version(hasProperty("-version") ? property("-version") : "SNAPSHOT") 11 | println "building version ${version}" 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | dependencies { 18 | compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 19 | compileOnly "org.jetbrains.kotlin:kotlin-reflect" 20 | 21 | testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 22 | testImplementation "org.jetbrains.kotlin:kotlin-reflect" 23 | testImplementation "org.jetbrains.kotlin:kotlin-test" 24 | testImplementation 'junit:junit:4.12' 25 | } 26 | 27 | jar { 28 | manifest { 29 | attributes 'Implementation-Title': 'hamkrest', 30 | 'Implementation-Vendor': 'com.natpryce', 31 | 'Implementation-Version': version 32 | } 33 | } 34 | 35 | task sourcesJar(type: Jar) { 36 | archiveClassifier = 'sources' 37 | from sourceSets.main.allSource 38 | } 39 | 40 | artifacts { 41 | archives sourcesJar 42 | } 43 | 44 | task javadocJar(type: Jar) { 45 | archiveClassifier = 'javadoc' 46 | from 'build/javadoc' 47 | } 48 | 49 | test { 50 | include 'com/natpryce/hamkrest/**' 51 | scanForTestClasses true 52 | reports { 53 | junitXml.enabled = true 54 | html.enabled = true 55 | } 56 | 57 | beforeTest { desc -> 58 | print "${desc.className.substring("com.natpryce.hamkrest.".length())}: ${desc.name.replace("_", " ")}" 59 | } 60 | afterTest { desc, result -> 61 | println " -> ${result.resultType}" 62 | } 63 | } 64 | 65 | artifacts { 66 | archives sourcesJar, javadocJar 67 | } 68 | 69 | signing { 70 | required { hasProperty("sign") || gradle.taskGraph.hasTask("uploadArchives") } 71 | sign configurations.archives 72 | } 73 | 74 | tasks.dokkaHtml { 75 | outputDirectory.set(layout.projectDirectory.dir("docs/dokka")) 76 | } 77 | 78 | task ossrhAuthentication { 79 | doFirst { 80 | if (!(project.hasProperty('ossrh.username') && project.hasProperty('ossrh.password'))) { 81 | throw new InvalidUserDataException("no OSSRH username and/or password!") 82 | } 83 | } 84 | } 85 | 86 | uploadArchives { 87 | dependsOn ossrhAuthentication 88 | 89 | repositories { 90 | mavenDeployer { 91 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 92 | 93 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 94 | authentication(userName: project.properties["ossrh.username"], 95 | password: project.properties["ossrh.password"]) 96 | } 97 | 98 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 99 | authentication(userName: project.properties["ossrh.username"], 100 | password: project.properties["ossrh.password"]) 101 | } 102 | 103 | pom.project { 104 | name 'HamKrest' 105 | packaging 'jar' 106 | description 'Matcher library for Kotlin. A port of Hamcrest, made more Kotlin-esque.' 107 | url 'https://github.com/npryce/hamkrest' 108 | 109 | scm { 110 | connection 'git@github.com:npryce/hamkrest.git' 111 | url 'https://github.com/npryce/hamkrest' 112 | } 113 | 114 | licenses { 115 | license { 116 | name 'Apache 2.0' 117 | url 'http://opensource.org/licenses/Apache-2.0' 118 | } 119 | } 120 | 121 | developers { 122 | developer { 123 | id 'npryce' 124 | name 'Nat Pryce' 125 | } 126 | developer { 127 | id 'dmcg' 128 | name 'Duncan McGregor' 129 | } 130 | } 131 | } 132 | } 133 | } 134 | } 135 | 136 | 137 | -------------------------------------------------------------------------------- /docs/describe.md: -------------------------------------------------------------------------------- 1 | # Extending how Hamkrest Describes Values 2 | 3 | Hamkrest provides a function, called [describe][], that generates readable representations of values for inclusion in 4 | assertion failure messages and the descriptions of matchers. 5 | 6 | For example: 7 | 8 | ``` 9 | describe(10) 10 | >>> 10 11 | 12 | describe(listOf("a","b","c")) 13 | >>> ["a", "b", "c"] 14 | 15 | describe(null) 16 | >>> null 17 | ``` 18 | 19 | The `describe` function generates reasonable representations of primitive types, null, Strings, Pairs, 20 | Triples and Collections, and falls back to calling `toString` when it has no specialised representation for 21 | a value. 22 | 23 | If the built-in representation is not suitable for your types, you can plug new representations into the `describe` function by registering a [JVM service][] of type [com.natpryce.hamkrest.ValueDescription][]. 24 | 25 | The ValueDescription service interface implements a single method, also called `describe`, which maps a value either to 26 | a String representation, or to `null` if the service cannot generate a representation for the value. When a 27 | ValueDescription service returns `null`, Hamkrest will pass the value to other registered ValueDescription services and, 28 | if no services can describe the value, generate a description using the default rules described above. 29 | 30 | **Tip**: You can define ValueDescription services in each module of your project, meaning they can use internal features 31 | of your modules to generate representations. 32 | 33 | [describe]: https://github.com/npryce/hamkrest/blob/master/src/main/kotlin/com/natpryce/hamkrest/describe.kt 34 | [JVM service]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html 35 | [com.natpryce.hamkrest.ValueDescription]: https://github.com/npryce/hamkrest/blob/master/src/main/kotlin/com/natpryce/hamkrest/ValueDescription.kt 36 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-case-sensitivity/-case-insensitive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CaseInsensitive 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

CaseInsensitive

68 |

Indicates that the match is case insensitive.

69 |
70 |
71 |
72 |
73 |
74 |
75 | 80 |
81 |
82 |
83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-case-sensitivity/-case-sensitive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CaseSensitive 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

CaseSensitive

68 |

Indicates that the match is case sensitive.

69 |
70 |
71 |
72 |
73 |
74 |
75 | 80 |
81 |
82 |
83 | 84 | 85 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-match-result/-match/to-string.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | toString 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

toString

68 |
69 |
open override fun toString(): String
70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-match-result/-mismatch/to-string.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | toString 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

toString

68 |
69 |
open override fun toString(): String
70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-matcher/-conjunction/description.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | description 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

description

68 |
69 |
open override val description: String

The description of this criteria.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-matcher/-disjunction/description.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | description 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

description

68 |
69 |
open override val description: String

The description of this criteria.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-matcher/-negation/-negation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Negation 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

Negation

68 |
69 |
constructor(negated: Matcher<T>)
70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-matcher/-negation/description.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | description 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

description

68 |
69 |
open override val description: String

The description of this criteria.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-matcher/-negation/negated-description.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | negatedDescription 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

negatedDescription

68 |
69 |
open override val negatedDescription: String

Describes the negation of this criteria.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-matcher/-negation/not.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | not 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

not

68 |
69 |
open operator override fun not(): Matcher<T>

Returns a matcher that matches the negation of this criteria.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-matcher/-primitive/-primitive.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Primitive 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

Primitive

68 |
69 |
constructor()
70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-matcher/as-predicate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | asPredicate 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

asPredicate

68 |
69 |
open fun asPredicate(): (T) -> Boolean

Returns this matcher as a predicate, that can be used for testing, finding and filtering collections and kotlin.sequences.Sequences.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-matcher/description.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | description 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

description

68 |
69 |
abstract override val description: String

The description of this criteria.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-matcher/invoke.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | invoke 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

invoke

68 |
69 |
abstract operator override fun invoke(actual: T): MatchResult

Reports whether the actual value meets the criteria and, if not, why it does not match.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-matcher/negated-description.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | negatedDescription 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

negatedDescription

68 |
69 |
open val negatedDescription: String

Describes the negation of this criteria.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-matcher/not.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | not 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

not

68 |
69 |
open operator fun not(): Matcher<T>

Returns a matcher that matches the negation of this criteria.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-self-describing/description.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | description 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

description

68 |
69 |
abstract val description: String

The description of this object

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/-string-matcher/-string-matcher.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StringMatcher 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

StringMatcher

68 |
69 |
constructor(caseSensitivity: S)
70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/absent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | absent 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

absent

68 |
69 |
fun <T> absent(): Matcher<T?>

Returns a matcher that reports if a value is null.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/anything.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | anything 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

anything

68 |
69 |
val anything: Matcher<Any?>

A Matcher that matches anything, always returning MatchResult.Match.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/identifier-to-words.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | identifierToWords 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

identifierToWords

68 |
69 |
fun identifierToWords(s: String): List<String>
70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/is-blank.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | isBlank 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

isBlank

68 |
69 |
val isBlank: Matcher<CharSequence>

Matches a char sequence if it is empty or consists solely of whitespace characters.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/is-empty-string.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | isEmptyString 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

isEmptyString

68 |
69 |
val isEmptyString: Matcher<CharSequence>

Matches a char sequence if it is empty (contains no characters).

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/is-empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | isEmpty 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

isEmpty

68 |
69 |
val isEmpty: Matcher<Collection<Any>>

Matches an empty collection.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/is-null-or-blank.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | isNullOrBlank 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

isNullOrBlank

68 |
69 |
val isNullOrBlank: Matcher<CharSequence?>

Matches a nullable char sequence if it is either null or empty or consists solely of whitespace characters.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/is-null-or-empty-string.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | isNullOrEmptyString 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

isNullOrEmptyString

68 |
69 |
val isNullOrEmptyString: Matcher<CharSequence?>

Matches a char sequence if it is either null or empty (contains no characters).

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/nothing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nothing 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

nothing

68 |
69 |
val nothing: Matcher<Any?>

A Matcher that matches nothing, always returning a MatchResult.Mismatch.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/hamkrest/com.natpryce.hamkrest/same-instance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | sameInstance 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 59 |
60 | 63 |
64 |
65 | 66 |
67 |

sameInstance

68 |
69 |
fun <T> sameInstance(expected: T): Matcher<T>

Returns a matcher that reports if a value is the same instance as expected value.

70 |
71 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/dokka/images/anchor-copy-button.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/dokka/images/arrow_down.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/dokka/images/burger.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/dokka/images/copy-icon.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/dokka/images/copy-successful-icon.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/dokka/images/footer-go-to-link.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/dokka/images/go-to-top-icon.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/dokka/images/homepage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/dokka/images/logo-icon.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/abstract-class-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/abstract-class.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/annotation-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/annotation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/class-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/class.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/enum-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/enum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/exception-class.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/field-value.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/field-variable.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/function.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/interface-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/interface.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/object.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/dokka/images/nav-icons/typealias-kotlin.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/dokka/images/theme-toggle.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/dokka/scripts/clipboard.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | window.addEventListener('load', () => { 6 | document.querySelectorAll('span.copy-icon').forEach(element => { 7 | element.addEventListener('click', (el) => copyElementsContentToClipboard(element)); 8 | }) 9 | 10 | document.querySelectorAll('span.anchor-icon').forEach(element => { 11 | element.addEventListener('click', (el) => { 12 | if(element.hasAttribute('pointing-to')){ 13 | const location = hrefWithoutCurrentlyUsedAnchor() + '#' + element.getAttribute('pointing-to') 14 | copyTextToClipboard(element, location) 15 | } 16 | }); 17 | }) 18 | }) 19 | 20 | const copyElementsContentToClipboard = (element) => { 21 | const selection = window.getSelection(); 22 | const range = document.createRange(); 23 | range.selectNodeContents(element.parentNode.parentNode); 24 | selection.removeAllRanges(); 25 | selection.addRange(range); 26 | 27 | copyAndShowPopup(element, () => selection.removeAllRanges()) 28 | } 29 | 30 | const copyTextToClipboard = (element, text) => { 31 | var textarea = document.createElement("textarea"); 32 | textarea.textContent = text; 33 | textarea.style.position = "fixed"; 34 | document.body.appendChild(textarea); 35 | textarea.select(); 36 | 37 | copyAndShowPopup(element, () => document.body.removeChild(textarea)) 38 | } 39 | 40 | const copyAndShowPopup = (element, after) => { 41 | try { 42 | document.execCommand('copy'); 43 | element.nextElementSibling.classList.add('active-popup'); 44 | setTimeout(() => { 45 | element.nextElementSibling.classList.remove('active-popup'); 46 | }, 1200); 47 | } catch (e) { 48 | console.error('Failed to write to clipboard:', e) 49 | } 50 | finally { 51 | if(after) after() 52 | } 53 | } 54 | 55 | const hrefWithoutCurrentlyUsedAnchor = () => window.location.href.split('#')[0] 56 | 57 | -------------------------------------------------------------------------------- /docs/dokka/scripts/navigation-loader.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | navigationPageText = fetch(pathToRoot + "navigation.html").then(response => response.text()) 6 | 7 | displayNavigationFromPage = () => { 8 | navigationPageText.then(data => { 9 | document.getElementById("sideMenu").innerHTML = data; 10 | }).then(() => { 11 | document.querySelectorAll(".overview > a").forEach(link => { 12 | link.setAttribute("href", pathToRoot + link.getAttribute("href")); 13 | }) 14 | }).then(() => { 15 | document.querySelectorAll(".sideMenuPart").forEach(nav => { 16 | if (!nav.classList.contains("hidden")) 17 | nav.classList.add("hidden") 18 | }) 19 | }).then(() => { 20 | revealNavigationForCurrentPage() 21 | }).then(() => { 22 | scrollNavigationToSelectedElement() 23 | }) 24 | document.querySelectorAll('.footer a[href^="#"]').forEach(anchor => { 25 | anchor.addEventListener('click', function (e) { 26 | e.preventDefault(); 27 | document.querySelector(this.getAttribute('href')).scrollIntoView({ 28 | behavior: 'smooth' 29 | }); 30 | }); 31 | }); 32 | } 33 | 34 | revealNavigationForCurrentPage = () => { 35 | let pageId = document.getElementById("content").attributes["pageIds"].value.toString(); 36 | let parts = document.querySelectorAll(".sideMenuPart"); 37 | let found = 0; 38 | do { 39 | parts.forEach(part => { 40 | if (part.attributes['pageId'].value.indexOf(pageId) !== -1 && found === 0) { 41 | found = 1; 42 | if (part.classList.contains("hidden")) { 43 | part.classList.remove("hidden"); 44 | part.setAttribute('data-active', ""); 45 | } 46 | revealParents(part) 47 | } 48 | }); 49 | pageId = pageId.substring(0, pageId.lastIndexOf("/")) 50 | } while (pageId.indexOf("/") !== -1 && found === 0) 51 | }; 52 | revealParents = (part) => { 53 | if (part.classList.contains("sideMenuPart")) { 54 | if (part.classList.contains("hidden")) 55 | part.classList.remove("hidden"); 56 | revealParents(part.parentNode) 57 | } 58 | }; 59 | 60 | scrollNavigationToSelectedElement = () => { 61 | let selectedElement = document.querySelector('div.sideMenuPart[data-active]') 62 | if (selectedElement == null) { // nothing selected, probably just the main page opened 63 | return 64 | } 65 | 66 | let hasIcon = selectedElement.querySelectorAll(":scope > div.overview span.nav-icon").length > 0 67 | 68 | // for instance enums also have children and are expandable, but are not package/module elements 69 | let isPackageElement = selectedElement.children.length > 1 && !hasIcon 70 | if (isPackageElement) { 71 | // if package is selected or linked, it makes sense to align it to top 72 | // so that you can see all the members it contains 73 | selectedElement.scrollIntoView(true) 74 | } else { 75 | // if a member within a package is linked, it makes sense to center it since it, 76 | // this should make it easier to look at surrounding members 77 | selectedElement.scrollIntoView({ 78 | behavior: 'auto', 79 | block: 'center', 80 | inline: 'center' 81 | }) 82 | } 83 | } 84 | 85 | /* 86 | This is a work-around for safari being IE of our times. 87 | It doesn't fire a DOMContentLoaded, presumabely because eventListener is added after it wants to do it 88 | */ 89 | if (document.readyState == 'loading') { 90 | window.addEventListener('DOMContentLoaded', () => { 91 | displayNavigationFromPage() 92 | }) 93 | } else { 94 | displayNavigationFromPage() 95 | } 96 | -------------------------------------------------------------------------------- /docs/dokka/scripts/sourceset_dependencies.js: -------------------------------------------------------------------------------- 1 | sourceset_dependencies='{":dokkaHtml/main":[]}' 2 | -------------------------------------------------------------------------------- /docs/dokka/scripts/symbol-parameters-wrapper_deferred.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | // helps with some corner cases where starts working already, 6 | // but the signature is not yet long enough to be wrapped 7 | (function() { 8 | const leftPaddingPx = 60; 9 | 10 | function createNbspIndent() { 11 | let indent = document.createElement("span"); 12 | indent.append(document.createTextNode("\u00A0\u00A0\u00A0\u00A0")); 13 | indent.classList.add("nbsp-indent"); 14 | return indent; 15 | } 16 | 17 | function wrapSymbolParameters(entry) { 18 | const symbol = entry.target; 19 | const symbolBlockWidth = entry.borderBoxSize && entry.borderBoxSize[0] && entry.borderBoxSize[0].inlineSize; 20 | 21 | // Even though the script is marked as `defer` and we wait for `DOMContentLoaded` event, 22 | // or if this block is a part of hidden tab, it can happen that `symbolBlockWidth` is 0, 23 | // indicating that something hasn't been loaded. 24 | // In this case, observer will be triggered onсe again when it will be ready. 25 | if (symbolBlockWidth > 0) { 26 | const node = symbol.querySelector(".parameters"); 27 | 28 | if (node) { 29 | // if window resize happened and observer was triggered, reset previously wrapped 30 | // parameters as they might not need wrapping anymore, and check again 31 | node.classList.remove("wrapped"); 32 | node.querySelectorAll(".parameter .nbsp-indent") 33 | .forEach(indent => indent.remove()); 34 | 35 | const innerTextWidth = Array.from(symbol.children) 36 | .filter(it => !it.classList.contains("block")) // blocks are usually on their own (like annotations), so ignore it 37 | .map(it => it.getBoundingClientRect().width) 38 | .reduce((a, b) => a + b, 0); 39 | 40 | // if signature text takes up more than a single line, wrap params for readability 41 | if (innerTextWidth > (symbolBlockWidth - leftPaddingPx)) { 42 | node.classList.add("wrapped"); 43 | node.querySelectorAll(".parameter").forEach(param => { 44 | // has to be a physical indent so that it can be copied. styles like 45 | // paddings and `::before { content: " " }` do not work for that 46 | param.prepend(createNbspIndent()); 47 | }); 48 | } 49 | } 50 | } 51 | } 52 | 53 | const symbolsObserver = new ResizeObserver(entries => entries.forEach(wrapSymbolParameters)); 54 | 55 | function initHandlers() { 56 | document.querySelectorAll("div.symbol").forEach(symbol => symbolsObserver.observe(symbol)); 57 | } 58 | 59 | if (document.readyState === 'loading') window.addEventListener('DOMContentLoaded', initHandlers); 60 | else initHandlers(); 61 | 62 | // ToDo: Add `unobserve` if dokka will be SPA-like: 63 | // https://github.com/w3c/csswg-drafts/issues/5155 64 | })(); 65 | -------------------------------------------------------------------------------- /docs/dokka/styles/font-jb-sans-auto.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | /* Light weight */ 6 | @font-face { 7 | font-family: 'JetBrains Sans'; 8 | src: url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-Light.woff2') format('woff2'), url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-Light.woff') format('woff'); 9 | font-weight: 300; 10 | font-style: normal; 11 | } 12 | /* Regular weight */ 13 | @font-face { 14 | font-family: 'JetBrains Sans'; 15 | src: url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-Regular.woff2') format('woff2'), url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-Regular.woff') format('woff'); 16 | font-weight: 400; 17 | font-style: normal; 18 | } 19 | /* SemiBold weight */ 20 | @font-face { 21 | font-family: 'JetBrains Sans'; 22 | src: url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-SemiBold.woff2') format('woff2'), url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-SemiBold.woff') format('woff'); 23 | font-weight: 600; 24 | font-style: normal; 25 | } 26 | 27 | @supports (font-variation-settings: normal) { 28 | @font-face { 29 | font-family: 'JetBrains Sans'; 30 | src: url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans.woff2') format('woff2 supports variations'), 31 | url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans.woff2') format('woff2-variations'), 32 | url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans.woff') format('woff-variations'); 33 | font-weight: 100 900; 34 | font-style: normal; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/dokka/styles/logo-styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | :root { 6 | --dokka-logo-image-url: url('../images/logo-icon.svg'); 7 | --dokka-logo-height: 50px; 8 | --dokka-logo-width: 50px; 9 | } 10 | -------------------------------------------------------------------------------- /docs/dokka/styles/prism.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. 3 | */ 4 | 5 | /* 6 | * Custom Dokka styles 7 | */ 8 | code .token { 9 | white-space: pre; 10 | } 11 | 12 | /** 13 | * Styles based on webhelp's prism.js styles 14 | * Changes: 15 | * - Since webhelp's styles are in .pcss, they use nesting which is not achievable in native CSS 16 | * so nested css blocks have been unrolled (like dark theme). 17 | * - Webhelp uses "Custom Class" prism.js plugin, so all of their prism classes are prefixed with "--prism". 18 | * Dokka doesn't seem to need this plugin at the moment, so all "--prism" prefixes have been removed. 19 | * - Removed all styles related to `pre` and `code` tags. Kotlinlang's resulting styles are so spread out and complicated 20 | * that it's difficult to gather in one place. Instead use code styles defined in the main Dokka styles, 21 | * which at the moment looks fairly similar. 22 | * 23 | * Based on prism.js default theme 24 | * Based on dabblet (http://dabblet.com) 25 | * @author Lea Verou 26 | */ 27 | 28 | .token.comment, 29 | .token.prolog, 30 | .token.doctype, 31 | .token.cdata { 32 | color: #8c8c8c; 33 | } 34 | 35 | .token.punctuation { 36 | color: #999; 37 | } 38 | 39 | .token.namespace { 40 | opacity: 0.7; 41 | } 42 | 43 | .token.property, 44 | .token.tag, 45 | .token.boolean, 46 | .token.number, 47 | .token.constant, 48 | .token.symbol, 49 | .token.deleted { 50 | color: #871094; 51 | } 52 | 53 | .token.selector, 54 | .token.attr-name, 55 | .token.string, 56 | .token.char, 57 | .token.builtin, 58 | .token.inserted { 59 | color: #067d17; 60 | } 61 | 62 | .token.operator, 63 | .token.entity, 64 | .token.url, 65 | .language-css .token.string, 66 | .style .token.string { 67 | color: #9a6e3a; 68 | /* This background color was intended by the author of this theme. */ 69 | background: hsla(0, 0%, 100%, 0.5); 70 | } 71 | 72 | .token.atrule, 73 | .token.attr-value, 74 | .token.keyword { 75 | font-size: inherit; /* to override .keyword */ 76 | color: #0033b3; 77 | } 78 | 79 | .token.function { 80 | color: #00627a; 81 | } 82 | 83 | .token.class-name { 84 | color: #000000; 85 | } 86 | 87 | .token.regex, 88 | .token.important, 89 | .token.variable { 90 | color: #871094; 91 | } 92 | 93 | .token.important, 94 | .token.bold { 95 | font-weight: bold; 96 | } 97 | .token.italic { 98 | font-style: italic; 99 | } 100 | 101 | .token.entity { 102 | cursor: help; 103 | } 104 | 105 | .token.operator { 106 | background: none; 107 | } 108 | 109 | /* 110 | * DARK THEME 111 | */ 112 | :root.theme-dark .token.comment, 113 | :root.theme-dark .token.prolog, 114 | :root.theme-dark .token.cdata { 115 | color: #808080; 116 | } 117 | 118 | :root.theme-dark .token.delimiter, 119 | :root.theme-dark .token.boolean, 120 | :root.theme-dark .token.keyword, 121 | :root.theme-dark .token.selector, 122 | :root.theme-dark .token.important, 123 | :root.theme-dark .token.atrule { 124 | color: #cc7832; 125 | } 126 | 127 | :root.theme-dark .token.operator, 128 | :root.theme-dark .token.punctuation, 129 | :root.theme-dark .token.attr-name { 130 | color: #a9b7c6; 131 | } 132 | 133 | :root.theme-dark .token.tag, 134 | :root.theme-dark .token.tag .punctuation, 135 | :root.theme-dark .token.doctype, 136 | :root.theme-dark .token.builtin { 137 | color: #e8bf6a; 138 | } 139 | 140 | :root.theme-dark .token.entity, 141 | :root.theme-dark .token.number, 142 | :root.theme-dark .token.symbol { 143 | color: #6897bb; 144 | } 145 | 146 | :root.theme-dark .token.property, 147 | :root.theme-dark .token.constant, 148 | :root.theme-dark .token.variable { 149 | color: #9876aa; 150 | } 151 | 152 | :root.theme-dark .token.string, 153 | :root.theme-dark .token.char { 154 | color: #6a8759; 155 | } 156 | 157 | :root.theme-dark .token.attr-value, 158 | :root.theme-dark .token.attr-value .punctuation { 159 | color: #a5c261; 160 | } 161 | 162 | :root.theme-dark .token.attr-value .punctuation:first-child { 163 | color: #a9b7c6; 164 | } 165 | 166 | :root.theme-dark .token.url { 167 | text-decoration: underline; 168 | 169 | color: #287bde; 170 | background: transparent; 171 | } 172 | 173 | :root.theme-dark .token.function { 174 | color: #ffc66d; 175 | } 176 | 177 | :root.theme-dark .token.regex { 178 | background: #364135; 179 | } 180 | 181 | :root.theme-dark .token.deleted { 182 | background: #484a4a; 183 | } 184 | 185 | :root.theme-dark .token.inserted { 186 | background: #294436; 187 | } 188 | 189 | :root.theme-dark .token.class-name { 190 | color: #a9b7c6; 191 | } 192 | 193 | :root.theme-dark .token.function { 194 | color: #ffc66d; 195 | } 196 | 197 | :root.theme-darkcode .language-css .token.property, 198 | :root.theme-darkcode .language-css, 199 | :root.theme-dark .token.property + .token.punctuation { 200 | color: #a9b7c6; 201 | } 202 | 203 | code.language-css .token.id { 204 | color: #ffc66d; 205 | } 206 | 207 | :root.theme-dark code.language-css .token.selector > .token.class, 208 | :root.theme-dark code.language-css .token.selector > .token.attribute, 209 | :root.theme-dark code.language-css .token.selector > .token.pseudo-class, 210 | :root.theme-dark code.language-css .token.selector > .token.pseudo-element { 211 | color: #ffc66d; 212 | } 213 | 214 | :root.theme-dark .language-plaintext .token { 215 | /* plaintext code should be colored as article text */ 216 | color: inherit !important; 217 | } 218 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Hamkrest 2 | 3 | A reimplementation of [Hamcrest](https://hamcrest.org/) to take advantage of Kotlin language features. 4 | 5 | - [Built In Matchers with Examples (wip)](matchers/) 6 | 7 | - [Dokka Documentation](dokka/) -------------------------------------------------------------------------------- /docs/matchers/equalTo.md: -------------------------------------------------------------------------------- 1 | # [`equalTo`](https://github.com/npryce/hamkrest/blob/9ce6f5882203b97130d1e85fa10818cbce1b7693/src/main/kotlin/com/natpryce/hamkrest/core_matchers.kt#L18-L27) 2 | 3 | A simple matcher that asserts equality. Works with `null` just like Kotlin's `==` operator. 4 | 5 | ### Assertion 6 | 7 | ``` 8 | assertThat("one", equalTo("two")) 9 | ``` 10 | 11 | ### Test Output 12 | 13 | ``` 14 | expected: a value that is equal to "two" 15 | but was: "one" 16 | ``` 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/matchers/index.md: -------------------------------------------------------------------------------- 1 | # Matcher Index 2 | 3 | ## [`equalTo`](equalTo.html) -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npryce/hamkrest/0cc6e6df36b72fd9788e4127c28777a89d310437/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /publish-release: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | `dirname $0`/b clean uploadArchives "$@" 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'hamkrest' 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/com/natpryce/hamkrest/SelfDescribing.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest 2 | 3 | /** 4 | * An object that can describe itself. 5 | */ 6 | interface SelfDescribing { 7 | /** 8 | * The description of this object 9 | */ 10 | val description: String 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/natpryce/hamkrest/ValueDescription.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest 2 | 3 | /** 4 | * A service interface for extensions to the [describe] function. 5 | */ 6 | interface ValueDescription { 7 | /** 8 | * Describes the value [v] or returns `null` to indicate that this service cannot describe the value, in which 9 | * case, other registered services are tried. If no services can describe the value, the [defaultDescription] 10 | * function is called. 11 | */ 12 | fun describe(v: Any?): String? 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/natpryce/hamkrest/assertion/assert.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest.assertion 2 | 3 | import com.natpryce.hamkrest.MatchResult 4 | import com.natpryce.hamkrest.Matcher 5 | import com.natpryce.hamkrest.describe 6 | import kotlin.reflect.KFunction1 7 | import kotlin.reflect.KFunction2 8 | 9 | private fun noMessage() = "" 10 | 11 | /** 12 | * Asserts that [criteria] matches [actual]. 13 | * On failure, the diagnostic is prefixed with the result of calling [message]. 14 | * @throws AssertionError if there is a mismatch 15 | */ 16 | fun assertThat(actual: T, criteria: Matcher, message: () -> String = ::noMessage) { 17 | val judgement = criteria(actual) 18 | if (judgement is MatchResult.Mismatch) { 19 | throw AssertionError( 20 | message().let { if (it.isEmpty()) it else "$it: " } + 21 | "expected: a value that ${describe(criteria)}\n" + 22 | "but ${describe(judgement)}") 23 | } 24 | } 25 | 26 | /** 27 | * Asserts that [criteria] matches [actual]. 28 | * On failure, the diagnostic is prefixed with the result of calling [message]. 29 | * @throws AssertionError if there is a mismatch 30 | */ 31 | fun assertThat(actual: T, criteria: KFunction1, message: () -> String = ::noMessage) { 32 | assertThat(actual, Matcher(criteria), message) 33 | } 34 | 35 | /** 36 | * Asserts that [criteria]([actual], [other]) returns true. 37 | * On failure, the diagnostic is prefixed with the result of calling [message]. 38 | * @throws AssertionError if there is a mismatch 39 | */ 40 | 41 | fun assertThat(actual: T, criteria: KFunction2, other: U, message: () -> String = ::noMessage) { 42 | assertThat(actual, Matcher(criteria, other), message) 43 | } 44 | 45 | 46 | /** 47 | * Asserts that [criteria] matches [actual]. On failure, the diagnostic is prefixed with [message]. 48 | * @throws AssertionError if there is a mismatch 49 | */ 50 | fun assertThat(message: String, actual: T, criteria: Matcher) { 51 | assertThat(actual, criteria, { message }) 52 | } 53 | 54 | /** 55 | * Asserts that [criteria] returns true for [actual]. On failure, the diagnostic is prefixed with [message]. 56 | * @throws AssertionError if there is a mismatch 57 | */ 58 | fun assertThat(message: String, actual: T, criteria: KFunction1) { 59 | assertThat(actual, criteria, { message }) 60 | } 61 | 62 | /** 63 | * Asserts that [criteria]([actual], [other]) returns true. On failure, the diagnostic is prefixed with [message]. 64 | * @throws AssertionError if there is a mismatch 65 | */ 66 | fun assertThat(message: String, actual: T, criteria: KFunction2, other: U) { 67 | assertThat(actual, criteria, other, { message }) 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/main/kotlin/com/natpryce/hamkrest/collection_matchers.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest 2 | 3 | import kotlin.reflect.KFunction1 4 | 5 | /** 6 | * Matches an [Iterable] if any element is matched by [elementMatcher]. 7 | */ 8 | fun anyElement(elementMatcher: Matcher) = object : Matcher.Primitive>() { 9 | override fun invoke(actual: Iterable): MatchResult = 10 | match(actual.any(elementMatcher.asPredicate())) { "was ${describe(actual)}" } 11 | 12 | override val description: String get() = "in which any element ${describe(elementMatcher)}" 13 | override val negatedDescription: String get() = "in which no element ${describe(elementMatcher)}" 14 | } 15 | 16 | /** 17 | * Matches an [Iterable] if any element is matched by [elementPredicate]. 18 | */ 19 | fun anyElement(elementPredicate: KFunction1) = anyElement(Matcher(elementPredicate)) 20 | 21 | /** 22 | * Matches an [Iterable] if all elements are matched by [elementMatcher]. 23 | */ 24 | fun allElements(elementMatcher: Matcher) = object : Matcher.Primitive>() { 25 | override fun invoke(actual: Iterable): MatchResult = 26 | match(actual.all(elementMatcher.asPredicate())) { "was ${describe(actual)}" } 27 | 28 | override val description: String get() = "in which all elements ${describe(elementMatcher)}" 29 | override val negatedDescription: String get() = "in which not all elements ${describe(elementMatcher)}" 30 | } 31 | 32 | /** 33 | * Matches an [Iterable] if all elements are matched by [elementPredicate]. 34 | */ 35 | fun allElements(elementPredicate: KFunction1) = allElements(Matcher(elementPredicate)) 36 | 37 | /** 38 | * Matches an empty collection. 39 | */ 40 | val isEmpty = Matcher(Collection::isEmpty) 41 | 42 | /** 43 | * Matches a collection with a size that matches [sizeMatcher]. 44 | */ 45 | fun hasSize(sizeMatcher: Matcher) = has(Collection::size, sizeMatcher) 46 | 47 | /** 48 | * Matches a collection that contains [element] 49 | * 50 | * See [Collection::contains] 51 | */ 52 | fun hasElement(element: T): Matcher> = object : Matcher.Primitive>() { 53 | override fun invoke(actual: Collection): MatchResult = 54 | match(element in actual) { "was ${describe(actual)}" } 55 | override val description: String get() = "contains ${describe(element)}" 56 | override val negatedDescription: String get() = "does not contain ${describe(element)}" 57 | } 58 | 59 | fun isIn(i: Iterable) : Matcher = object : Matcher.Primitive() { 60 | override fun invoke(actual: T) = match(actual in i) {"was not in ${describe(i)}"} 61 | override val description: String get() = "is in ${describe(i)}" 62 | override val negatedDescription: String get() = "is not in ${describe(i)}" 63 | } 64 | 65 | fun isIn(vararg elements: T) = isIn(elements.toList()) 66 | -------------------------------------------------------------------------------- /src/main/kotlin/com/natpryce/hamkrest/describe.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest 2 | 3 | import java.util.ServiceLoader 4 | 5 | /** 6 | * Formats [v] to be included in a description. Strings are delimited with quotes and elements of tuples, ranges, 7 | * iterable collections and maps are (recursively) described. A null reference is described as `null`. 8 | * For anything else, the result of [Any.toString] is used. 9 | * 10 | * @param v the value to be described. 11 | */ 12 | fun describe(v: Any?): String = 13 | ServiceLoader.load(ValueDescription::class.java).asSequence() 14 | .mapNotNull { it.describe(v) } 15 | .firstOrNull() ?: defaultDescription(v) 16 | 17 | 18 | /** 19 | * The default description of a value, used when a value is not described by any registered [ValueDescription] services. 20 | */ 21 | private fun defaultDescription(v: Any?): String = when (v) { 22 | null -> "null" 23 | is SelfDescribing -> v.description 24 | is String -> "\"" + v.replace("\\", "\\\\").replace("\"", "\\\"") + "\"" 25 | is Pair<*, *> -> Pair(describe(v.first), describe(v.second)).toString() 26 | is Triple<*, *, *> -> Triple(describe(v.first), describe(v.second), describe(v.third)).toString() 27 | is ClosedRange<*> -> "${describe(v.start)}..${describe(v.endInclusive)}" 28 | is Set<*> -> v.map(::describe).joinToString(prefix = "{", separator = ", ", postfix = "}") 29 | is Collection<*> -> v.map(::describe).joinToString(prefix = "[", separator = ", ", postfix = "]") 30 | is Map<*, *> -> v.entries.map { "${describe(it.key)}:${describe(it.value)}" }.joinToString(prefix = "{", separator = ", ", postfix = "}") 31 | else -> v.toString() 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/com/natpryce/hamkrest/internal.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest 2 | 3 | import java.util.ArrayList 4 | 5 | internal fun match(comparisonResult: Boolean, describeMismatch: () -> String): MatchResult = 6 | if (comparisonResult) { 7 | MatchResult.Match 8 | } else { 9 | MatchResult.Mismatch(describeMismatch()) 10 | } 11 | 12 | internal fun identifierToDescription(id: String) = identifierToWords(id).joinToString(" ") 13 | 14 | internal fun identifierToNegatedDescription(id: String): String { 15 | val words = identifierToWords(id) 16 | val first = words[0] 17 | val rest = words.drop(1).joinToString(" ") 18 | 19 | return when (first) { 20 | "is" -> "is not ${rest}" 21 | "has" -> "does not have ${rest}" 22 | else -> "not ${first} ${rest}" 23 | } 24 | } 25 | 26 | fun identifierToWords(s: String): List { 27 | val words: MutableList = ArrayList() 28 | val buf = StringBuilder() 29 | 30 | for ((prev, c) in (s[0] + s).zip(s)) { 31 | if (isWordStart(prev, c)) { 32 | if (buf.isNotEmpty()) { 33 | words.add(buf.toString()) 34 | buf.setLength(0) 35 | } 36 | } 37 | 38 | if (isWordPart(c)) { 39 | buf.append(c.toLowerCase()) 40 | } 41 | } 42 | 43 | words.add(buf.toString()) 44 | 45 | return words 46 | } 47 | 48 | internal fun isWordPart(c: Char): Boolean = c.isLetterOrDigit() 49 | 50 | internal fun isWordStart(prev: Char, c: Char): Boolean = when { 51 | c.isLetter() != prev.isLetter() -> true 52 | prev.isLowerCase() && c.isUpperCase() -> true 53 | else -> false 54 | } 55 | -------------------------------------------------------------------------------- /src/main/kotlin/com/natpryce/hamkrest/number_matchers.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest 2 | 3 | /** 4 | * Returns a matcher that reports if a value is equal to an [expected] value, withing a range of +/- [error]. 5 | */ 6 | fun closeTo(expected: Float, error: Float = 0.00001f): Matcher = _closeTo(expected, error) 7 | 8 | /** 9 | * Returns a matcher that reports if a value is equal to an [expected] value, withing a range of +/- [error]. 10 | */ 11 | fun closeTo(expected: Double, error: Double = 0.00001): Matcher = _closeTo(expected, error) 12 | 13 | private fun _closeTo(expected: T, error: T): Matcher = 14 | object : Matcher { 15 | override fun invoke(actual: T): MatchResult = match(delta(actual, expected, error) <= 0.0) { 16 | "a numeric value ${describe(actual)} differed by ${describe(delta(actual, expected, error))} more than error ${describe(error)}" 17 | } 18 | override val description: String get() = "is equal to ${describe(expected)} within ${describe(error)}" 19 | override val negatedDescription: String get() = "is not equal to ${describe(expected)} within ${describe(error)}" 20 | 21 | private fun delta(actual: T, expected: T, error: T) = Math.abs(actual.toDouble() - expected.toDouble()) - error.toDouble() 22 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/natpryce/hamkrest/assertion/assert_tests.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest.assertion 2 | 3 | import com.natpryce.hamkrest.equalTo 4 | import com.natpryce.hamkrest.matches 5 | import org.junit.Test 6 | import kotlin.text.RegexOption.DOT_MATCHES_ALL 7 | import kotlin.text.RegexOption.MULTILINE 8 | 9 | class AssertOutput { 10 | @Test 11 | fun produces_ide_friendly_error_message() { 12 | try { 13 | assertThat("foo", equalTo("bar")) 14 | } catch (e: AssertionError) { 15 | assertThat(e.message!!, matches(Regex("expected: .*but was: .*", setOf(MULTILINE, DOT_MATCHES_ALL)))) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/kotlin/com/natpryce/hamkrest/assertions.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest 2 | 3 | import kotlin.test.assertEquals 4 | import kotlin.test.fail 5 | 6 | fun assertMismatchWithDescription(expectedDescription: String, m: MatchResult) { 7 | when (m) { 8 | MatchResult.Match -> fail("unexpected match") 9 | is MatchResult.Mismatch -> { 10 | assertEquals(expectedDescription, m.description) 11 | } 12 | } 13 | } 14 | 15 | fun assertMismatch(m: MatchResult) { 16 | if (m == MatchResult.Match) fail("unexpected match") 17 | } 18 | 19 | fun assertMatch(result: MatchResult) { 20 | assertEquals(MatchResult.Match, result) 21 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/natpryce/hamkrest/collection_matchers_tests.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest 2 | 3 | import com.natpryce.hamkrest.assertion.assertThat 4 | import org.junit.Test 5 | 6 | 7 | class Contains { 8 | @Test 9 | fun contains_any() { 10 | assertThat(listOf(1, 2, 3, 4), anyElement(equalTo(3))) 11 | assertThat(listOf(1, 2, 3, 4), anyElement(greaterThanOrEqualTo(4))) 12 | assertThat(emptyList(), !anyElement(anything)) 13 | } 14 | 15 | @Test 16 | fun contains_element() { 17 | assertThat(listOf(1, 2, 3, 4), hasElement(1)) 18 | assertThat(listOf(1, 2, 3, 4), hasElement(2)) 19 | assertThat(listOf(1, 2, 3, 4), !hasElement(0)) 20 | } 21 | 22 | @Test 23 | fun empty_sequence_never_meets_any_elements() { 24 | assertThat(emptyList(), !anyElement(anything)) 25 | } 26 | 27 | @Test 28 | fun any_elements_can_be_passed_a_function_reference() { 29 | assertThat(listOf("1", "2", " "), anyElement(String::isBlank)) 30 | } 31 | 32 | @Test 33 | fun all_elements() { 34 | assertThat(listOf(1), allElements(equalTo(1))) 35 | assertThat(listOf(1, 2, 3, 4), allElements(greaterThan(0))) 36 | assertThat(listOf(1, 2, 3, 4), !allElements(equalTo(1))) 37 | } 38 | 39 | @Test 40 | fun empty_sequence_always_meets_all_elements() { 41 | assertThat(emptyList(), allElements(anything)) 42 | } 43 | 44 | @Test 45 | fun all_elements_can_be_passed_a_function_reference() { 46 | assertThat(listOf("1", "2", " "), !allElements(String::isBlank)) 47 | } 48 | } 49 | 50 | class IsIn { 51 | @Test 52 | fun is_in_collection() { 53 | assertThat(1, isIn(listOf(1, 2, 3, 4))) 54 | assertThat(2, isIn(listOf(1, 2, 3, 4))) 55 | assertThat(3, isIn(listOf(1, 2, 3, 4))) 56 | assertThat(4, isIn(listOf(1, 2, 3, 4))) 57 | 58 | assertThat(5, !isIn(listOf(1, 2, 3, 4))) 59 | 60 | assertThat(3, !isIn(emptyList())) 61 | 62 | assertThat(1, isIn(setOf(1, 2, 3, 4))) 63 | assertThat(3, isIn(setOf(1, 2, 3, 4))) 64 | } 65 | 66 | @Test 67 | fun is_in_varargs_treated_as_list() { 68 | assertThat(1, isIn(1, 2, 3, 4)) 69 | assertThat(2, isIn(1, 2, 3, 4)) 70 | } 71 | } 72 | 73 | 74 | class CollectionSize { 75 | @Test 76 | fun size() { 77 | val l = listOf(1,2,3) 78 | 79 | assertThat(l, hasSize(equalTo(3))) 80 | assertThat(l, hasSize(greaterThan(2))) 81 | assertThat(l, !hasSize(lessThan(3))) 82 | } 83 | 84 | @Test 85 | fun size_description() { 86 | assertThat(hasSize(greaterThan(3)).description, equalTo("has size that is greater than 3")) 87 | assertThat((!hasSize(greaterThan(3))).description, equalTo("does not have size that is greater than 3")) 88 | } 89 | 90 | @Test 91 | fun empty() { 92 | assertThat(emptyList(), isEmpty) 93 | assertThat(listOf(1,2,3), !isEmpty) 94 | } 95 | 96 | @Test 97 | fun empty_description() { 98 | assertThat(isEmpty.description, equalTo("is empty")) 99 | assertThat((!isEmpty).description, equalTo("is not empty")) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/test/kotlin/com/natpryce/hamkrest/describe_tests.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest 2 | 3 | import com.natpryce.hamkrest.assertion.assertThat 4 | import org.junit.Test 5 | 6 | class DelimitingValuesInStrings { 7 | @Test 8 | fun quotes_around_string_values_and_escaped_within() { 9 | assertThat(describe("hello, \"bob\""), equalTo("\"hello, \\\"bob\\\"\"")) 10 | } 11 | 12 | @Test 13 | fun pairs() { 14 | assertThat(describe(Pair("x", "y")), equalTo("(\"x\", \"y\")")) 15 | } 16 | 17 | @Test 18 | fun triples() { 19 | assertThat(describe(Triple("x", "y", "z")), equalTo("(\"x\", \"y\", \"z\")")) 20 | } 21 | 22 | @Test 23 | fun iterables() { 24 | assertThat(describe(listOf(1, 2)), equalTo("[1, 2]")) 25 | assertThat(describe(listOf("1", "2")), equalTo("""["1", "2"]""")) 26 | } 27 | 28 | @Test 29 | fun sets() { 30 | assertThat(describe(setOf(1, 2)), equalTo("{1, 2}")) 31 | assertThat(describe(setOf("1", "2")), equalTo("""{"1", "2"}""")) 32 | } 33 | 34 | @Test 35 | fun ranges() { 36 | assertThat(describe(1..8), equalTo("1..8")) 37 | } 38 | 39 | @Test 40 | fun maps() { 41 | assertThat(describe(mapOf("a" to 1, "b" to 2)), equalTo("""{"a":1, "b":2}""")) 42 | } 43 | 44 | @Test 45 | fun self_describing_objects() { 46 | val d = object : SelfDescribing { 47 | override val description = "d" 48 | } 49 | 50 | assertThat(describe(d), equalTo("d")) 51 | } 52 | 53 | class IterableButNotCollection(private vararg val elements: String) : Iterable { 54 | override fun iterator() = elements.iterator() 55 | override fun toString() = elements.joinToString("/") 56 | } 57 | 58 | @Test 59 | fun does_not_generate_collectionlike_description_for_iterables_that_are_not_collections() { 60 | val thing = IterableButNotCollection("foo", "bar", "baz") 61 | assertThat(describe(thing), equalTo("foo/bar/baz")) 62 | } 63 | } 64 | 65 | 66 | class NeedsCustomDescription 67 | 68 | class ExampleDescribeExtension : ValueDescription { 69 | override fun describe(v: Any?) = 70 | when (v) { 71 | is NeedsCustomDescription -> "the-custom-description" 72 | else -> null 73 | } 74 | } 75 | 76 | class DescribeExtensionPointTests { 77 | @Test 78 | fun can_extend_describe_function_with_jvm_services() { 79 | assertThat(describe(NeedsCustomDescription()), equalTo("the-custom-description")) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/kotlin/com/natpryce/hamkrest/internal_tests.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest 2 | 3 | import com.natpryce.hamkrest.assertion.assertThat 4 | import org.junit.Test 5 | 6 | class Utilities { 7 | @Test 8 | fun splits_identifier_into_words() { 9 | val cases = listOf( 10 | "identifier" to listOf("identifier"), 11 | "an_identifier" to listOf("an", "identifier"), 12 | "anIdentifier" to listOf("an", "identifier"), 13 | "farenheit451" to listOf("farenheit", "451"), 14 | "i_got_99_problems" to listOf("i", "got", "99", "problems") 15 | ) 16 | 17 | for ((identifier, words) in cases) { 18 | assertThat("${describe(identifier)} to words", identifierToWords(identifier), equalTo(words)) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/natpryce/hamkrest/number_matchers.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest 2 | 3 | import org.junit.Test 4 | import kotlin.test.assertEquals 5 | 6 | class CloseTo { 7 | @Test 8 | fun close() { 9 | assertMatch(closeTo(3.0, 0.1)(3.01)) 10 | assertMatch(closeTo(3.0)(3.0)) 11 | assertMatch(closeTo(3.0f, 0.1f)(3.01f)) 12 | assertMatch(closeTo(3.0f)(3.0f)) 13 | } 14 | 15 | @Test 16 | fun not_close() { 17 | assertMismatchWithDescription("a numeric value 4.0 differed by 0.9 more than error 0.1", closeTo(3.0, 0.1)(4.0)) 18 | } 19 | 20 | @Test 21 | fun description() { 22 | assertEquals("is equal to 3.0 within 0.1", closeTo(3.0, 0.1).description) 23 | assertEquals("is not equal to 3.0 within 0.01", closeTo(3.0, 0.01).negatedDescription) 24 | assertEquals("is equal to 3.0 within 0.1", closeTo(3.0f, 0.1f).description) 25 | assertEquals("is not equal to 3.0 within 0.01", closeTo(3.0f, 0.01f).negatedDescription) 26 | } 27 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/natpryce/hamkrest/string_matchers_tests.kt: -------------------------------------------------------------------------------- 1 | package com.natpryce.hamkrest 2 | 3 | import com.natpryce.hamkrest.assertion.assertThat 4 | import org.junit.Test 5 | 6 | class RegexMatching { 7 | val pattern = Regex("a(b*)a") 8 | 9 | @Test 10 | fun entire_string_matches_regex() { 11 | assertThat("abba", matches(pattern)) 12 | assertThat("aba", matches(pattern)) 13 | assertThat("abbbbbba", matches(pattern)) 14 | assertThat("aha!", !matches(pattern)) 15 | assertThat("yabba dabba doo", !matches(pattern)) 16 | } 17 | 18 | @Test 19 | fun string_contains_regex() { 20 | assertThat("abba", contains(pattern)) 21 | assertThat("aba", contains(pattern)) 22 | assertThat("abbbbbba", contains(pattern)) 23 | assertThat("aha!", !matches(pattern)) 24 | assertThat("yabba dabba doo", contains(pattern)) 25 | } 26 | 27 | } 28 | 29 | class ContainsSubstring { 30 | @Test 31 | fun contains_substring() { 32 | assertThat("qwerty", containsSubstring("qwe")) 33 | assertThat("qwerty", containsSubstring("wert")) 34 | assertThat("qwerty", containsSubstring("erty")) 35 | } 36 | 37 | @Test 38 | fun contains_substring_can_specify_case_sensitivity() { 39 | assertThat("qwerty", containsSubstring("WERT").caseInsensitive()) 40 | assertThat("qwerty", !containsSubstring("WERT").caseInsensitive().caseSensitive()) 41 | } 42 | 43 | @Test 44 | fun description() { 45 | assertThat(containsSubstring("foo").description, equalTo("contains substring \"foo\"")) 46 | assertThat(containsSubstring("foo").caseInsensitive().description, equalTo("contains substring \"foo\" (case insensitive)")) 47 | } 48 | } 49 | 50 | class StringPrefixAndSuffix { 51 | @Test 52 | fun prefix() { 53 | assertThat("qwerty", startsWith("q")) 54 | assertThat("qwerty", startsWith("qwe")) 55 | assertThat("qwerty", !startsWith("Q")) 56 | assertThat("qwerty", startsWith("")) 57 | } 58 | 59 | @Test 60 | fun prefix_can_specify_case_sensitivity() { 61 | assertThat("qwerty", startsWith("Q").caseInsensitive()) 62 | assertThat("qwerty", !startsWith("Q").caseInsensitive().caseSensitive()) 63 | } 64 | 65 | @Test 66 | fun suffix() { 67 | assertThat("qwerty", endsWith("y")) 68 | assertThat("qwerty", endsWith("rty")) 69 | assertThat("qwerty", !endsWith("Y")) 70 | assertThat("qwerty", endsWith("")) 71 | } 72 | 73 | @Test 74 | fun suffix_can_specify_case_sensitivity() { 75 | assertThat("qwerty", endsWith("Y").caseInsensitive()) 76 | assertThat("qwerty", !endsWith("Y").caseInsensitive().caseSensitive()) 77 | } 78 | } 79 | 80 | class NullableString { 81 | @Test 82 | fun null_or_empty_works_on_null_or_empty_string() { 83 | assertThat(null, isNullOrEmptyString) 84 | assertThat("", isNullOrEmptyString) 85 | } 86 | 87 | @Test 88 | fun null_or_blank_works_on_null_or_blank_string() { 89 | assertThat(null, isNullOrBlank) 90 | assertThat("", isNullOrBlank) 91 | assertThat(" ", isNullOrBlank) 92 | } 93 | } 94 | 95 | class CaseInsensitiveEquals { 96 | @Test 97 | fun equal_to_ignoring_case() { 98 | assertThat("qwerty", equalToIgnoringCase("qwerty")) 99 | assertThat("qwerty", equalToIgnoringCase("QWERTY")) 100 | assertThat("QWERTY", equalToIgnoringCase("qwerty")) 101 | assertThat("qwerty", !equalToIgnoringCase(" qwerty")) 102 | assertThat("qwerty", !equalToIgnoringCase(null)) 103 | assertThat(null, equalToIgnoringCase(null)) 104 | assertThat(null, !equalToIgnoringCase("qwerty")) 105 | } 106 | 107 | @Test 108 | fun description() { 109 | assertThat(equalToIgnoringCase("foo").description, equalToIgnoringCase("""IS EQUAL (IGNORING CASE) TO "foo"""")) 110 | assertThat(equalToIgnoringCase(null).description, equalToIgnoringCase("""IS EQUAL (IGNORING CASE) TO NULL""")) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/services/com.natpryce.hamkrest.ValueDescription: -------------------------------------------------------------------------------- 1 | com.natpryce.hamkrest.ExampleDescribeExtension 2 | -------------------------------------------------------------------------------- /tag-release: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | version=$1 3 | 4 | if [ -z ${version} ] 5 | then 6 | echo "ERROR no tag given; last tag:" `git tag -l | grep -E '[0-9]+(.[0-9]+){3}' | sort -t. -k 1,1nr -k 2,2nr -k 3,3nr -k 4,4nr | head -1` 7 | exit 1 8 | else 9 | git tag -u sw@natpryce.com ${version} -m "tagging version ${version}" 10 | fi 11 | --------------------------------------------------------------------------------