aboutsummaryrefslogtreecommitdiff
path: root/test-tools/src/main/kotlin/matchers/content/ContentMatchersDsl.kt
blob: 41d73378c20ea07148863e5f8946698b512e48bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package matchers.content

import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.isEqualTo
import org.jetbrains.dokka.pages.*
import org.jetbrains.dokka.test.tools.matchers.content.*
import kotlin.reflect.KClass

// entry point:
fun ContentNode.assertNode(block: ContentMatcherBuilder<ContentComposite>.() -> 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<T : ContentComposite> @PublishedApi internal constructor(private val kclass: KClass<T>) {
    @PublishedApi
    internal val children = mutableListOf<MatcherElement>()
    internal val assertions = mutableListOf<T.() -> Unit>()

    fun build() = CompositeMatcher(kclass, children) { assertions.forEach { it() } }

    // part of DSL that cannot be defined as an extension
    operator fun String.unaryPlus() {
        children += TextMatcher(this)
    }
}

fun <T : ContentComposite> ContentMatcherBuilder<T>.check(assertion: T.() -> Unit) {
    assertions += assertion
}

inline fun <reified S : ContentComposite> ContentMatcherBuilder<*>.composite(
    block: ContentMatcherBuilder<S>.() -> Unit
) {
    children += ContentMatcherBuilder(S::class).apply(block).build()
}

inline fun <reified S : ContentNode> ContentMatcherBuilder<*>.node(noinline assertions: S.() -> Unit = {}) {
    children += NodeMatcher(S::class, assertions)
}

fun ContentMatcherBuilder<*>.skipAllNotMatching() {
    children += Anything
}


// Convenience functions:
fun ContentMatcherBuilder<*>.group(block: ContentMatcherBuilder<ContentGroup>.() -> Unit) = composite(block)

fun ContentMatcherBuilder<*>.header(expectedLevel: Int? = null, block: ContentMatcherBuilder<ContentHeader>.() -> Unit) =
    composite<ContentHeader> {
        block()
        check { if (expectedLevel != null) assertThat(this::level).isEqualTo(expectedLevel) }
    }

fun ContentMatcherBuilder<*>.p(block: ContentMatcherBuilder<ContentGroup>.() -> Unit) =
    composite<ContentGroup> {
        block()
        check { assertThat(this::style).contains(TextStyle.Paragraph) }
    }

fun ContentMatcherBuilder<*>.link(block: ContentMatcherBuilder<ContentLink>.() -> Unit) = composite(block)

fun ContentMatcherBuilder<*>.table(block: ContentMatcherBuilder<ContentTable>.() -> Unit) = composite(block)

fun ContentMatcherBuilder<*>.platformHinted(block: ContentMatcherBuilder<ContentGroup>.() -> Unit) =
    composite<PlatformHintedContent> { group(block) }

fun ContentMatcherBuilder<*>.br() = node<ContentBreakLine>()

fun ContentMatcherBuilder<*>.somewhere(block: ContentMatcherBuilder<*>.() -> Unit) {
    skipAllNotMatching()
    block()
    skipAllNotMatching()
}