diff options
Diffstat (limited to 'dokka-subprojects/plugin-base/src/test/kotlin/utils')
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 |
