package matchers.content import assertk.assertThat import assertk.assertions.contains import assertk.assertions.isEqualTo import assertk.assertions.matches import org.jetbrains.dokka.model.withDescendants import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.test.tools.matchers.content.* import kotlin.reflect.KClass // entry point: fun ContentNode.assertNode(block: ContentMatcherBuilder.() -> Unit) { val matcher = ContentMatcherBuilder(ContentComposite::class).apply(block).build() try { matcher.tryMatch(this) } catch (e: MatcherError) { throw AssertionError(e.message + "\n" + matcher.toDebugString(e.anchor, e.anchorAfter)) } } // DSL: @DslMarker annotation class ContentMatchersDsl @ContentMatchersDsl class ContentMatcherBuilder @PublishedApi internal constructor(private val kclass: KClass) { @PublishedApi internal val children = mutableListOf() internal val assertions = mutableListOf Unit>() fun build() = CompositeMatcher(kclass, childrenOrSkip()) { assertions.forEach { it() } } // part of DSL that cannot be defined as an extension operator fun String.unaryPlus() { children += TextMatcher(this) } private fun childrenOrSkip() = if (children.isEmpty() && assertions.isNotEmpty()) listOf(Anything) else children } fun ContentMatcherBuilder.check(assertion: T.() -> Unit) { assertions += assertion } private val ContentComposite.extractedText get() = withDescendants().filterIsInstance().joinToString(separator = "") { it.text } fun ContentMatcherBuilder.hasExactText(expected: String) { assertions += { assertThat(this::extractedText).isEqualTo(expected) } } fun ContentMatcherBuilder.textMatches(pattern: Regex) { assertions += { assertThat(this::extractedText).matches(pattern) } } inline fun ContentMatcherBuilder<*>.composite( block: ContentMatcherBuilder.() -> Unit ) { children += ContentMatcherBuilder(S::class).apply(block).build() } inline fun ContentMatcherBuilder<*>.node(noinline assertions: S.() -> Unit = {}) { children += NodeMatcher(S::class, assertions) } fun ContentMatcherBuilder<*>.skipAllNotMatching() { children += Anything } // Convenience functions: fun ContentMatcherBuilder<*>.group(block: ContentMatcherBuilder.() -> Unit) = composite(block) fun ContentMatcherBuilder<*>.header(expectedLevel: Int? = null, block: ContentMatcherBuilder.() -> Unit) = composite { block() check { if (expectedLevel != null) assertThat(this::level).isEqualTo(expectedLevel) } } fun ContentMatcherBuilder<*>.p(block: ContentMatcherBuilder.() -> Unit) = composite { block() check { assertThat(this::style).contains(TextStyle.Paragraph) } } fun ContentMatcherBuilder<*>.link(block: ContentMatcherBuilder.() -> Unit) = composite(block) fun ContentMatcherBuilder<*>.table(block: ContentMatcherBuilder.() -> Unit) = composite(block) fun ContentMatcherBuilder<*>.platformHinted(block: ContentMatcherBuilder.() -> Unit) = composite { group(block) } fun ContentMatcherBuilder<*>.br() = node() fun ContentMatcherBuilder<*>.somewhere(block: ContentMatcherBuilder<*>.() -> Unit) { skipAllNotMatching() block() skipAllNotMatching() } fun ContentMatcherBuilder<*>.divergentGroup(block: ContentMatcherBuilder.() -> Unit) = composite(block) fun ContentMatcherBuilder.divergentInstance(block: ContentMatcherBuilder.() -> Unit) = composite(block) fun ContentMatcherBuilder.before(block: ContentMatcherBuilder.() -> Unit) = composite(block) fun ContentMatcherBuilder.divergent(block: ContentMatcherBuilder.() -> Unit) = composite(block) fun ContentMatcherBuilder.after(block: ContentMatcherBuilder.() -> Unit) = composite(block)