aboutsummaryrefslogtreecommitdiff
path: root/test-tools/src/main/kotlin/matchers/content/ContentMatchersDsl.kt
blob: 67c0e6927725a75cf1dd65b62978acaad25691fe (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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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<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, 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 <T : ContentComposite> ContentMatcherBuilder<T>.check(assertion: T.() -> Unit) {
    assertions += assertion
}

private val ContentComposite.extractedText
    get() = withDescendants().filterIsInstance<ContentText>().joinToString(separator = "") { it.text }

fun <T : ContentComposite> ContentMatcherBuilder<T>.hasExactText(expected: String) {
    assertions += {
        assertThat(this::extractedText).isEqualTo(expected)
    }
}

fun <T : ContentComposite> ContentMatcherBuilder<T>.textMatches(pattern: Regex) {
    assertions += {
        assertThat(this::extractedText).matches(pattern)
    }
}

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()
}

fun ContentMatcherBuilder<*>.divergentGroup(block: ContentMatcherBuilder<ContentDivergentGroup>.() -> Unit) =
    composite(block)

fun ContentMatcherBuilder<ContentDivergentGroup>.divergentInstance(block: ContentMatcherBuilder<ContentDivergentInstance>.() -> Unit) =
    composite(block)

fun ContentMatcherBuilder<ContentDivergentInstance>.before(block: ContentMatcherBuilder<ContentComposite>.() -> Unit) =
    composite(block)

fun ContentMatcherBuilder<ContentDivergentInstance>.divergent(block: ContentMatcherBuilder<ContentComposite>.() -> Unit) =
    composite(block)

fun ContentMatcherBuilder<ContentDivergentInstance>.after(block: ContentMatcherBuilder<ContentComposite>.() -> Unit) =
    composite(block)