aboutsummaryrefslogtreecommitdiff
path: root/dokka-subprojects/plugin-base/src/test/kotlin/utils
diff options
context:
space:
mode:
Diffstat (limited to 'dokka-subprojects/plugin-base/src/test/kotlin/utils')
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/utils/HtmlUtils.kt20
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/utils/ModelUtils.kt43
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/utils/TagsAnnotations.kt42
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/utils/TestUtils.kt94
-rw-r--r--dokka-subprojects/plugin-base/src/test/kotlin/utils/contentUtils.kt355
5 files changed, 554 insertions, 0 deletions
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/utils/HtmlUtils.kt b/dokka-subprojects/plugin-base/src/test/kotlin/utils/HtmlUtils.kt
new file mode 100644
index 00000000..cb700a94
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/utils/HtmlUtils.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package utils
+
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import com.fasterxml.jackson.module.kotlin.readValue
+import org.jetbrains.dokka.base.renderers.html.SearchRecord
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Element
+import org.jsoup.select.Elements
+
+internal fun TestOutputWriter.navigationHtml(): Element = contents.getValue("navigation.html").let { Jsoup.parse(it) }
+
+internal fun TestOutputWriter.pagesJson(): List<SearchRecord> = jacksonObjectMapper().readValue(contents.getValue("scripts/pages.json"))
+
+internal fun Elements.selectNavigationGrid(): Element {
+ return this.select("div.overview").select("span.nav-link-grid").single()
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/utils/ModelUtils.kt b/dokka-subprojects/plugin-base/src/test/kotlin/utils/ModelUtils.kt
new file mode 100644
index 00000000..fc7e9a2c
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/utils/ModelUtils.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package utils
+
+import org.jetbrains.dokka.DokkaConfigurationImpl
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.plugability.DokkaPlugin
+
+abstract class AbstractModelTest(val path: String? = null, val pkg: String) : ModelDSL(), AssertDSL {
+
+ fun inlineModelTest(
+ query: String,
+ platform: String = "jvm",
+ prependPackage: Boolean = true,
+ cleanupOutput: Boolean = true,
+ pluginsOverrides: List<DokkaPlugin> = emptyList(),
+ configuration: DokkaConfigurationImpl? = null,
+ block: DModule.() -> Unit
+ ) {
+ val testConfiguration = configuration ?: dokkaConfiguration {
+ suppressObviousFunctions = false
+ sourceSets {
+ sourceSet {
+ sourceRoots = listOf("src/")
+ analysisPlatform = platform
+ classpath += jvmStdlibPath!!
+ }
+ }
+ }
+ val prepend = path.let { p -> p?.let { "|$it\n" } ?: "" } + if (prependPackage) "|package $pkg" else ""
+
+ testInline(
+ query = ("$prepend\n$query").trim().trimIndent(),
+ configuration = testConfiguration,
+ cleanupOutput = cleanupOutput,
+ pluginOverrides = pluginsOverrides
+ ) {
+ documentablesTransformationStage = block
+ }
+ }
+}
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/utils/TagsAnnotations.kt b/dokka-subprojects/plugin-base/src/test/kotlin/utils/TagsAnnotations.kt
new file mode 100644
index 00000000..a81b1dae
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/utils/TagsAnnotations.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package utils
+
+import org.junit.jupiter.api.Tag
+
+
+/**
+ * Run a test only for descriptors, not symbols.
+ *
+ * In theory, these tests can be fixed
+ */
+@Target(
+ AnnotationTarget.CLASS,
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.PROPERTY_SETTER
+)
+@Retention(
+ AnnotationRetention.RUNTIME
+)
+@Tag("onlyDescriptors")
+annotation class OnlyDescriptors(val reason: String = "")
+
+/**
+ * Run a test only for descriptors, not symbols.
+ *
+ * These tests cannot be fixed until Analysis API does not support MPP
+ */
+@Target(
+ AnnotationTarget.CLASS,
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.PROPERTY_SETTER
+)
+@Retention(
+ AnnotationRetention.RUNTIME
+)
+@Tag("onlyDescriptorsMPP")
+annotation class OnlyDescriptorsMPP(val reason: String = "")
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/utils/TestUtils.kt b/dokka-subprojects/plugin-base/src/test/kotlin/utils/TestUtils.kt
new file mode 100644
index 00000000..39ac4b23
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/utils/TestUtils.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package utils
+
+import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.doc.*
+import org.jetbrains.dokka.model.doc.P
+import kotlin.collections.orEmpty
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.asserter
+import kotlin.test.fail
+
+@DslMarker
+annotation class TestDSL
+
+@TestDSL
+abstract class ModelDSL : BaseAbstractTest() {
+ operator fun Documentable?.div(name: String): Documentable? =
+ this?.children?.find { it.name == name }
+
+ inline fun <reified T : Documentable> Documentable?.cast(): T =
+ (this as? T).assertNotNull()
+}
+
+@TestDSL
+interface AssertDSL {
+ infix fun Any?.equals(other: Any?) = assertEquals(other, this)
+ infix fun Collection<Any>?.allEquals(other: Any?) =
+ this?.onEach { it equals other } ?: run { fail("Collection is empty") }
+ infix fun <T> Collection<T>?.exists(e: T) {
+ assertTrue(this.orEmpty().isNotEmpty(), "Collection cannot be null or empty")
+ assertTrue(this!!.any{it == e}, "Collection doesn't contain $e")
+ }
+
+ infix fun <T> Collection<T>?.counts(n: Int) = this.orEmpty().assertCount(n)
+
+ infix fun <T> T?.notNull(name: String): T = this.assertNotNull(name)
+
+ fun <T> Collection<T>.assertCount(n: Int, prefix: String = "") =
+ assertEquals(n, count(), "${prefix}Expected $n, got ${count()}")
+}
+
+/*
+ * TODO replace with kotlin.test.assertContains after migrating to Kotlin 1.5+
+ */
+internal fun <T> assertContains(iterable: Iterable<T>, element: T, ) {
+ asserter.assertTrue(
+ { "Expected the collection to contain the element.\nCollection <$iterable>, element <$element>." },
+ iterable.contains(element)
+ )
+}
+
+inline fun <reified T : Any> Any?.assertIsInstance(name: String): T =
+ this.let { it as? T } ?: throw AssertionError("$name should not be null")
+
+fun TagWrapper.text(): String = when (val t = this) {
+ is NamedTagWrapper -> "${t.name}: [${t.root.text()}]"
+ else -> t.root.text()
+}
+
+fun DocTag.text(): String = when (val t = this) {
+ is Text -> t.body
+ is Code -> t.children.joinToString("\n") { it.text() }
+ is P -> t.children.joinToString("") { it.text() } + "\n"
+ else -> t.children.joinToString("") { it.text() }
+}
+
+fun <T : Documentable> T?.comments(): String = docs().map { it.text() }
+ .joinToString(separator = "\n") { it }
+
+fun <T> T?.assertNotNull(name: String = ""): T = this ?: throw AssertionError("$name should not be null")
+
+fun <T : Documentable> T?.docs() = this?.documentation.orEmpty().values.flatMap { it.children }
+
+val DClass.supers
+ get() = supertypes.flatMap { it.component2() }
+
+val Bound.name: String?
+ get() = when (this) {
+ is Nullable -> inner.name
+ is DefinitelyNonNullable -> inner.name
+ is TypeParameter -> name
+ is PrimitiveJavaType -> name
+ is TypeConstructor -> dri.classNames
+ is JavaObject -> "Object"
+ is Void -> "void"
+ is Dynamic -> "dynamic"
+ is UnresolvedBound -> "<ERROR CLASS>"
+ is TypeAliased -> typeAlias.name
+ }
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/utils/contentUtils.kt b/dokka-subprojects/plugin-base/src/test/kotlin/utils/contentUtils.kt
new file mode 100644
index 00000000..3ca0bd2d
--- /dev/null
+++ b/dokka-subprojects/plugin-base/src/test/kotlin/utils/contentUtils.kt
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package utils
+
+import matchers.content.*
+import org.jetbrains.dokka.model.dfs
+import org.jetbrains.dokka.pages.*
+import kotlin.test.assertEquals
+
+//TODO: Try to unify those functions after update to 1.4
+fun ContentMatcherBuilder<*>.functionSignature(
+ annotations: Map<String, Set<String>>,
+ visibility: String,
+ modifier: String,
+ keywords: Set<String>,
+ name: String,
+ returnType: String? = null,
+ vararg params: Pair<String, ParamAttributes>
+) =
+ platformHinted {
+ bareSignature(annotations, visibility, modifier, keywords, name, returnType, *params)
+ }
+
+fun ContentMatcherBuilder<*>.bareSignature(
+ annotations: Map<String, Set<String>>,
+ visibility: String,
+ modifier: String,
+ keywords: Set<String>,
+ name: String,
+ returnType: String? = null,
+ vararg params: Pair<String, ParamAttributes>
+) = group {
+ annotations.entries.forEach {
+ group {
+ unwrapAnnotation(it)
+ }
+ }
+ if (visibility.isNotBlank()) +"$visibility "
+ if (modifier.isNotBlank()) +"$modifier "
+ +("${keywords.joinToString("") { "$it " }}fun ")
+ link { +name }
+ +"("
+ if (params.isNotEmpty()) {
+ group {
+ params.forEachIndexed { id, (n, t) ->
+ group {
+ t.annotations.forEach {
+ unwrapAnnotation(it)
+ }
+ t.keywords.forEach {
+ +it
+ }
+
+ +"$n: "
+ group { link { +(t.type) } }
+ if (id != params.lastIndex)
+ +", "
+ }
+ }
+ }
+ }
+ +")"
+ if (returnType != null) {
+ +(": ")
+ group {
+ link {
+ +(returnType)
+ }
+ }
+ }
+}
+
+fun ContentMatcherBuilder<*>.classSignature(
+ annotations: Map<String, Set<String>>,
+ visibility: String,
+ modifier: String,
+ keywords: Set<String>,
+ name: String,
+ vararg params: Pair<String, ParamAttributes>,
+ parent: String? = null
+) = group {
+ annotations.entries.forEach {
+ group {
+ unwrapAnnotation(it)
+ }
+ }
+ if (visibility.isNotBlank()) +"$visibility "
+ if (modifier.isNotBlank()) +"$modifier "
+ +("${keywords.joinToString("") { "$it " }}class ")
+ link { +name }
+ if (params.isNotEmpty()) {
+ +"("
+ group {
+ params.forEachIndexed { id, (n, t) ->
+ group {
+ t.annotations.forEach {
+ unwrapAnnotation(it)
+ }
+ t.keywords.forEach {
+ +it
+ }
+
+ +"$n: "
+ group { link { +(t.type) } }
+ if (id != params.lastIndex)
+ +", "
+ }
+ }
+ }
+ +")"
+ }
+ if (parent != null) {
+ +(" : ")
+ link {
+ +(parent)
+ }
+ }
+}
+
+fun ContentMatcherBuilder<*>.functionSignatureWithReceiver(
+ annotations: Map<String, Set<String>>,
+ visibility: String?,
+ modifier: String?,
+ keywords: Set<String>,
+ receiver: String,
+ name: String,
+ returnType: String? = null,
+ vararg params: Pair<String, ParamAttributes>
+) =
+ platformHinted {
+ bareSignatureWithReceiver(annotations, visibility, modifier, keywords, receiver, name, returnType, *params)
+ }
+
+fun ContentMatcherBuilder<*>.bareSignatureWithReceiver(
+ annotations: Map<String, Set<String>>,
+ visibility: String?,
+ modifier: String?,
+ keywords: Set<String>,
+ receiver: String,
+ name: String,
+ returnType: String? = null,
+ vararg params: Pair<String, ParamAttributes>
+) = group { // TODO: remove it when double wrapping for signatures will be resolved
+ annotations.entries.forEach {
+ group {
+ unwrapAnnotation(it)
+ }
+ }
+ if (visibility != null && visibility.isNotBlank()) +"$visibility "
+ if (modifier != null && modifier.isNotBlank()) +"$modifier "
+ +("${keywords.joinToString("") { "$it " }}fun ")
+ group {
+ link { +receiver }
+ }
+ +"."
+ link { +name }
+ +"("
+ if (params.isNotEmpty()) {
+ group {
+ params.forEachIndexed { id, (n, t) ->
+ group {
+ t.annotations.forEach {
+ unwrapAnnotation(it)
+ }
+ t.keywords.forEach {
+ +it
+ }
+
+ +"$n: "
+ group { link { +(t.type) } }
+ if (id != params.lastIndex)
+ +", "
+ }
+ }
+ }
+ }
+ +")"
+ if (returnType != null) {
+ +(": ")
+ group {
+ link {
+ +(returnType)
+ }
+ }
+ }
+}
+
+fun ContentMatcherBuilder<*>.propertySignature(
+ annotations: Map<String, Set<String>>,
+ visibility: String,
+ modifier: String,
+ keywords: Set<String>,
+ preposition: String,
+ name: String,
+ type: String? = null,
+ value: String? = null
+) {
+ group {
+ header { +"Package-level declarations" }
+ skipAllNotMatching()
+ }
+ tabbedGroup {
+ group {
+ skipAllNotMatching()
+ tab(BasicTabbedContentType.PROPERTY) {
+ header{ + "Properties" }
+ table {
+ group {
+ link { +name }
+ divergentGroup {
+ divergentInstance {
+ divergent {
+ group {
+ group {
+ annotations.entries.forEach {
+ group {
+ unwrapAnnotation(it)
+ }
+ }
+ if (visibility.isNotBlank()) +"$visibility "
+ if (modifier.isNotBlank()) +"$modifier "
+ +("${keywords.joinToString("") { "$it " }}$preposition ")
+ link { +name }
+ if (type != null) {
+ +(": ")
+ group {
+ link {
+ +(type)
+ }
+ }
+ }
+ if (value != null) {
+ +(" = $value")
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+fun ContentMatcherBuilder<*>.typealiasSignature(name: String, expressionTarget: String) {
+ group {
+ header { +"Package-level declarations" }
+ skipAllNotMatching()
+ }
+ group {
+ group {
+ tab(BasicTabbedContentType.TYPE) {
+ header{ + "Types" }
+ table {
+ group {
+ link { +name }
+ divergentGroup {
+ divergentInstance {
+ group {
+ group {
+ group {
+ group {
+ +"typealias "
+ group {
+ group {
+ link { +name }
+ }
+ skipAllNotMatching()
+ }
+ +" = "
+ group {
+ link { +expressionTarget }
+ }
+ }
+ }
+ }
+ }
+ }
+ skipAllNotMatching()
+ }
+ }
+ skipAllNotMatching()
+ }
+ skipAllNotMatching()
+ }
+ }
+ }
+}
+
+fun ContentMatcherBuilder<*>.pWrapped(text: String) =
+ group {// TODO: remove it when double wrapping for descriptions will be resolved
+ group { +text }
+ }
+
+fun ContentMatcherBuilder<*>.unnamedTag(tag: String, content: ContentMatcherBuilder<ContentGroup>.() -> Unit) =
+ group {
+ header(4) { +tag }
+ content()
+ }
+
+fun ContentMatcherBuilder<*>.comment(content: ContentMatcherBuilder<ContentGroup>.() -> Unit) =
+ group {
+ group {
+ content()
+ }
+ }
+
+fun ContentMatcherBuilder<*>.unwrapAnnotation(elem: Map.Entry<String, Set<String>>) {
+ group {
+ +"@"
+ link { +elem.key }
+ if(elem.value.isNotEmpty()) {
+ +"("
+ elem.value.forEach {
+ group {
+ +("$it = ")
+ skipAllNotMatching()
+ }
+ }
+ +")"
+ }
+ }
+}
+inline fun<reified T> PageNode.contentPage(name: String, block: T.() -> Unit) {
+ (dfs { it.name == name } as? T).assertNotNull("The page `$name` is not found").block()
+}
+
+fun ClasslikePageNode.assertHasFunctions(vararg expectedFunctionName: String) {
+ val functions = this.findSectionWithName("Functions").assertNotNull("Functions")
+ val functionsName = functions.children.map { (it.dfs { it is ContentText } as ContentText).text }
+ assertEquals(expectedFunctionName.toList(), functionsName)
+}
+
+fun ClasslikePageNode.findSectionWithName(name: String) : ContentNode? {
+ var sectionHeader: ContentHeader? = null
+ return content.dfs { node ->
+ node.children.filterIsInstance<ContentHeader>().any { header ->
+ header.children.firstOrNull { it is ContentText && it.text == name }?.also { sectionHeader = header } != null
+ }
+ }?.children?.dropWhile { child -> child != sectionHeader }?.drop(1)?.firstOrNull()
+}
+
+data class ParamAttributes(
+ val annotations: Map<String, Set<String>>,
+ val keywords: Set<String>,
+ val type: String
+)
+
+fun RootPageNode.findTestType(packageName: String, name: String) =
+ children.single { it.name == packageName }.children.single { it.name == name } as ContentPage