diff options
Diffstat (limited to 'dokka-subprojects/plugin-base/src/test/kotlin/content')
19 files changed, 6967 insertions, 0 deletions
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/ContentInDescriptionTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/ContentInDescriptionTest.kt new file mode 100644 index 00000000..a278795d --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/ContentInDescriptionTest.kt @@ -0,0 +1,142 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.doc.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ContentInDescriptionTest : BaseAbstractTest() { + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + classpath += jvmStdlibPath!! + } + } + } + + val expectedDescription = Description( + CustomDocTag( + listOf( + P( + listOf( + Text("Hello World! Docs with period issue, e.g."), + Text(String(Character.toChars(160)), params = mapOf("content-type" to "html")), + Text("this.") + ) + ) + ), + params = emptyMap(), + name = "MARKDOWN_FILE" + ) + ) + + @Test + fun `nbsp is handled as code in kotlin`() { + testInline( + """ + |/src/main/kotlin/sample/ParentKt.kt + |package sample; + |/** + | * Hello World! Docs with period issue, e.g. this. + | */ + |public class ParentKt { + |} + """.trimIndent(), configuration + ) { + documentablesMergingStage = { + val classlike = it.packages.flatMap { it.classlikes }.find { it.name == "ParentKt" } + + assertTrue(classlike != null) + assertEquals(expectedDescription, classlike.documentation.values.first().children.first()) + } + } + } + + @Test + fun `nbsp is handled as code in java`() { + testInline( + """ + |/src/main/kotlin/sample/Parent.java + |package sample; + |/** + | * Hello World! Docs with period issue, e.g. this. + | */ + |public class Parent { + |} + """.trimIndent(), configuration + ) { + documentablesMergingStage = { + val classlike = it.packages.flatMap { it.classlikes }.find { it.name == "Parent" } + + assertTrue(classlike != null) + assertEquals(expectedDescription, classlike.documentation.values.first().children.first()) + } + } + } + + @Test + fun `same documentation in java and kotlin when nbsp is present`() { + testInline( + """ + |/src/main/kotlin/sample/Parent.java + |package sample; + |/** + | * Hello World! Docs with period issue, e.g. this. + | */ + |public class Parent { + |} + | + |/src/main/kotlin/sample/ParentKt.kt + |package sample; + |/** + | * Hello World! Docs with period issue, e.g. this. + | */ + |public class ParentKt { + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + val java = module.packages.flatMap { it.classlikes }.first { it.name == "Parent" } + val kotlin = module.packages.flatMap { it.classlikes }.first { it.name == "ParentKt" } + + assertEquals(java.documentation.values.first(), kotlin.documentation.values.first()) + } + } + } + + @Test + fun `text surrounded by angle brackets is not removed`() { + testInline( + """ + |/src/main/kotlin/sample/Foo.kt + |package sample + |/** + | * My example `CodeInline<Bar>` + | * ``` + | * CodeBlock<Bar> + | * ``` + | */ + |class Foo { + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + val cls = module.packages.flatMap { it.classlikes }.first { it.name == "Foo" } + val documentation = cls.documentation.values.first() + val docTags = documentation.children.single().root.children + + assertEquals("CodeInline<Bar>", ((docTags[0].children[1] as CodeInline).children.first() as Text).body) + assertEquals("CodeBlock<Bar>", ((docTags[1] as CodeBlock).children.first() as Text).body) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/HighlightingTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/HighlightingTest.kt new file mode 100644 index 00000000..a7fb2bde --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/HighlightingTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.pages.* +import kotlin.test.Test +import kotlin.test.assertTrue + +class HighlightingTest : BaseAbstractTest() { + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOf(commonStdlibPath!!, jvmStdlibPath!!) + externalDocumentationLinks = listOf(stdlibExternalDocumentationLink) + } + } + } + + @Test + fun `open suspend fun`() { + testInline( + """ + |/src/main/kotlin/test/Test.kt + |package example + | + | open suspend fun simpleFun(): String = "Celebrimbor" + """, + configuration + ) { + pagesTransformationStage = { module -> + val symbol = (module.dfs { it.name == "simpleFun" } as MemberPageNode).content + .dfs { it is ContentGroup && it.dci.kind == ContentKind.Symbol } + val children = symbol?.children + + for (it in listOf( + Pair(0, TokenStyle.Keyword), Pair(1, TokenStyle.Keyword), Pair(2, TokenStyle.Keyword), + Pair(4, TokenStyle.Punctuation), Pair(5, TokenStyle.Punctuation), Pair(6, TokenStyle.Operator) + )) + assertTrue(children?.get(it.first)?.style?.contains(it.second) == true) + assertTrue(children?.get(3)?.children?.first()?.style?.contains(TokenStyle.Function) == true) + } + } + } + + @Test + fun `plain typealias of plain class with annotation`() { + testInline( + """ + |/src/main/kotlin/common/Test.kt + |package example + | + |@MustBeDocumented + |@Target(AnnotationTarget.TYPEALIAS) + |annotation class SomeAnnotation + | + |@SomeAnnotation + |typealias PlainTypealias = Int + | + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { module -> + val symbol = (module.dfs { it.name == "example" } as PackagePageNode).content + .dfs { it is ContentGroup && it.dci.kind == ContentKind.Symbol } + val children = symbol?.children + + for (it in listOf( + Pair(1, TokenStyle.Keyword), Pair(3, TokenStyle.Operator) + )) + assertTrue(children?.get(it.first)?.style?.contains(it.second) == true) + val annotation = children?.first()?.children?.first() + + assertTrue(annotation?.children?.get(0)?.style?.contains(TokenStyle.Annotation) == true) + assertTrue(annotation?.children?.get(1)?.children?.first()?.style?.contains(TokenStyle.Annotation) == true) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt new file mode 100644 index 00000000..7293b53c --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt @@ -0,0 +1,351 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.annotations + +import matchers.content.* +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.base.utils.firstNotNullOfOrNull +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.ContentText +import org.jetbrains.dokka.pages.MemberPageNode +import org.jetbrains.dokka.pages.PackagePageNode +import utils.ParamAttributes +import utils.assertNotNull +import utils.bareSignature +import utils.propertySignature +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + + +class ContentForAnnotationsTest : BaseAbstractTest() { + + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + classpath += jvmStdlibPath!! + } + } + } + + @Test + fun `function with documented annotation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, + | AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD + |) + |@Retention(AnnotationRetention.SOURCE) + |@MustBeDocumented + |annotation class Fancy + | + | + |@Fancy + |fun function(@Fancy abc: String): String { + | return "Hello, " + abc + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + mapOf("Fancy" to emptySet()), + "", + "", + emptySet(), + "function", + "String", + "abc" to ParamAttributes(mapOf("Fancy" to emptySet()), emptySet(), "String") + ) + } + } + } + + } + } + } + } + + @Test + fun `function with undocumented annotation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, + | AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD + |) + |@Retention(AnnotationRetention.SOURCE) + |annotation class Fancy + | + |@Fancy + |fun function(@Fancy abc: String): String { + | return "Hello, " + abc + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + "String", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + + } + } + } + } + + @Test + fun `property with undocumented annotation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@Suppress + |val property: Int = 6 + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + propertySignature(emptyMap(), "", "", emptySet(), "val", "property", "Int", "6") + } + } + } + } + + @Test + fun `property with documented annotation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@MustBeDocumented + |annotation class Fancy + | + |@Fancy + |val property: Int = 6 + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + propertySignature(mapOf("Fancy" to emptySet()), "", "", emptySet(), "val", "property", "Int", "6") + } + } + } + } + + + @Test + fun `rich documented annotation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@MustBeDocumented + |@Retention(AnnotationRetention.SOURCE) + |@Target(AnnotationTarget.PROPERTY) + |annotation class BugReport( + | val assignedTo: String = "[none]", + | val testCase: KClass<ABC> = ABC::class, + | val status: Status = Status.UNCONFIRMED, + | val ref: Reference = Reference(value = 1), + | val reportedBy: Array<Reference>, + | val showStopper: Boolean = false + | val previousReport: BugReport? + |) { + | enum class Status { + | UNCONFIRMED, CONFIRMED, FIXED, NOTABUG + | } + | class ABC + |} + |annotation class Reference(val value: Long) + |annotation class ReferenceReal(val value: Double) + | + | + |@BugReport( + | assignedTo = "me", + | testCase = BugReport.ABC::class, + | status = BugReport.Status.FIXED, + | ref = Reference(value = 2u), + | reportedBy = [Reference(value = 2UL), Reference(value = 4L), + | ReferenceReal(value = 4.9), ReferenceReal(value = 2f)], + | showStopper = true, + | previousReport = null + |) + |val ltint: Int = 5 + """.trimIndent(), testConfiguration + ) { + documentablesCreationStage = { modules -> + + fun expectedAnnotationValue(name: String, value: AnnotationParameterValue) = AnnotationValue(Annotations.Annotation( + dri = DRI("test", name), + params = mapOf("value" to value), + scope = Annotations.AnnotationScope.DIRECT, + mustBeDocumented = false + )) + val property = modules.flatMap { it.packages }.flatMap { it.properties }.first() + val annotation = property.extra[Annotations]?.let { + it.directAnnotations.entries.firstNotNullOfOrNull { (_, annotations) -> annotations.firstOrNull() } + } + val annotationParams = annotation?.params ?: emptyMap() + + assertEquals(expectedAnnotationValue("Reference", IntValue(2)), annotationParams["ref"]) + + val reportedByParam = ArrayValue(listOf( + expectedAnnotationValue("Reference", LongValue(2)), + expectedAnnotationValue("Reference", LongValue(4)), + expectedAnnotationValue("ReferenceReal", DoubleValue(4.9)), + expectedAnnotationValue("ReferenceReal", FloatValue(2f)) + )) + assertEquals(reportedByParam, annotationParams["reportedBy"]) + assertEquals(BooleanValue(true), annotationParams["showStopper"]) + assertEquals(NullValue, annotationParams["previousReport"]) + } + + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + propertySignature( + mapOf( + "BugReport" to setOf( + "assignedTo", + "testCase", + "status", + "ref", + "reportedBy", + "showStopper", + "previousReport" + ) + ), "", "", emptySet(), "val", "ltint", "Int", "5" + ) + } + } + } + } + + @Test + fun `JvmName for property with setter and getter`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + |@get:JvmName("xd") + |@set:JvmName("asd") + |var property: String + | get() = "" + | set(value) {} + """.trimIndent(), testConfiguration + ) { + documentablesCreationStage = { modules -> + fun expectedAnnotation(name: String) = Annotations.Annotation( + dri = DRI("kotlin.jvm", "JvmName"), + params = mapOf("name" to StringValue(name)), + scope = Annotations.AnnotationScope.DIRECT, + mustBeDocumented = true + ) + + val property = modules.flatMap { it.packages }.flatMap { it.properties }.first() + val getterAnnotation = property.getter?.extra?.get(Annotations)?.let { + it.directAnnotations.entries.firstNotNullOfOrNull { (_, annotations) -> annotations.firstOrNull() } + } + val setterAnnotation = property.getter?.extra?.get(Annotations)?.let { + it.directAnnotations.entries.firstNotNullOfOrNull { (_, annotations) -> annotations.firstOrNull() } + } + + assertEquals(expectedAnnotation("xd"), getterAnnotation) + assertTrue(getterAnnotation?.mustBeDocumented!!) + assertEquals(Annotations.AnnotationScope.DIRECT, getterAnnotation.scope) + + assertEquals(expectedAnnotation("asd"), setterAnnotation) + assertTrue(setterAnnotation?.mustBeDocumented!!) + assertEquals(Annotations.AnnotationScope.DIRECT, setterAnnotation.scope) + } + } + } + + @Test + fun `annotated bounds in Kotlin`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |@MustBeDocumented + |@Target(AnnotationTarget.TYPE_PARAMETER) + |annotation class Hello(val bar: String) + |fun <T: @Hello("abc") String> foo(arg: String): List<T> = TODO() + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { root -> + val fooPage = root.dfs { it.name == "foo" } as MemberPageNode + fooPage.content.dfs { it is ContentText && it.text == "Hello" }.assertNotNull() + } + } + } + + @Test + fun `annotated bounds in Java`() { + testInline( + """ + |/src/main/java/demo/AnnotationTest.java + |package demo; + |import java.lang.annotation.*; + |import java.util.List; + |@Documented + |@Target({ElementType.TYPE_USE, ElementType.TYPE}) + |@interface Hello { + | public String bar() default ""; + |} + |public class AnnotationTest { + | public <T extends @Hello(bar = "baz") String> List<T> foo() { + | return null; + | } + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { root -> + val fooPage = root.dfs { it.name == "foo" } as MemberPageNode + fooPage.content.dfs { it is ContentText && it.text == "Hello" }.assertNotNull() + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/FileLevelJvmNameTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/FileLevelJvmNameTest.kt new file mode 100644 index 00000000..5809d7df --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/FileLevelJvmNameTest.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.annotations + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.Annotations +import org.jetbrains.dokka.model.StringValue +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import kotlin.test.assertEquals + +class FileLevelJvmNameTest : BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + classpath += jvmStdlibPath!! + } + } + } + + companion object { + private const val functionTest = + """ + |/src/main/kotlin/test/source.kt + |@file:JvmName("CustomJvmName") + |package test + | + |fun function(abc: String): String { + | return "Hello, " + abc + |} + """ + + private const val extensionFunctionTest = + """ + |/src/main/kotlin/test/source.kt + |@file:JvmName("CustomJvmName") + |package test + | + |fun String.function(abc: String): String { + | return "Hello, " + abc + |} + """ + + private const val propertyTest = + """ + |/src/main/kotlin/test/source.kt + |@file:JvmName("CustomJvmName") + |package test + | + |val property: String + | get() = "" + """ + + private const val extensionPropertyTest = + """ + |/src/main/kotlin/test/source.kt + |@file:JvmName("CustomJvmName") + |package test + | + |val String.property: String + | get() = "" + """ + } + + @ParameterizedTest + @ValueSource(strings = [functionTest, extensionFunctionTest]) + fun `jvm name should be included in functions extra`(query: String) { + testInline( + query.trimIndent(), testConfiguration + ) { + documentablesCreationStage = { modules -> + val expectedAnnotation = Annotations.Annotation( + dri = DRI("kotlin.jvm", "JvmName"), + params = mapOf("name" to StringValue("CustomJvmName")), + scope = Annotations.AnnotationScope.FILE, + mustBeDocumented = true + ) + val function = modules.flatMap { it.packages }.first().functions.first() + val annotation = function.extra[Annotations]?.fileLevelAnnotations?.entries?.first()?.value?.single() + assertEquals(emptyMap(), function.extra[Annotations]?.directAnnotations) + assertEquals(expectedAnnotation, annotation) + assertEquals(expectedAnnotation.scope, annotation?.scope) + assertEquals(expectedAnnotation.mustBeDocumented, annotation?.mustBeDocumented) + } + } + } + + @ParameterizedTest + @ValueSource(strings = [propertyTest, extensionPropertyTest]) + fun `jvm name should be included in properties extra`(query: String) { + testInline( + query.trimIndent(), testConfiguration + ) { + documentablesCreationStage = { modules -> + val expectedAnnotation = Annotations.Annotation( + dri = DRI("kotlin.jvm", "JvmName"), + params = mapOf("name" to StringValue("CustomJvmName")), + scope = Annotations.AnnotationScope.FILE, + mustBeDocumented = true + ) + val properties = modules.flatMap { it.packages }.first().properties.first() + val annotation = properties.extra[Annotations]?.fileLevelAnnotations?.entries?.first()?.value?.single() + assertEquals(emptyMap(), properties.extra[Annotations]?.directAnnotations) + assertEquals(expectedAnnotation, annotation) + assertEquals(expectedAnnotation.scope, annotation?.scope) + assertEquals(expectedAnnotation.mustBeDocumented, annotation?.mustBeDocumented) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/JavaDeprecatedTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/JavaDeprecatedTest.kt new file mode 100644 index 00000000..5a2ff93e --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/JavaDeprecatedTest.kt @@ -0,0 +1,144 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.annotations + +import matchers.content.* +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.base.transformers.documentables.deprecatedAnnotation +import org.jetbrains.dokka.base.transformers.documentables.isDeprecated +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.properties.WithExtraProperties +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.ContentStyle +import utils.pWrapped +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class JavaDeprecatedTest : BaseAbstractTest() { + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `should assert util functions for deprecation`() { + testInline( + """ + |/src/main/kotlin/deprecated/DeprecatedJavaClass.java + |package deprecated + | + |@Deprecated(forRemoval = true) + |public class DeprecatedJavaClass {} + """.trimIndent(), + testConfiguration + ) { + documentablesTransformationStage = { module -> + val deprecatedClass = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "DeprecatedJavaClass" } + + val isDeprecated = (deprecatedClass as WithExtraProperties<out Documentable>).isDeprecated() + assertTrue(isDeprecated) + + val deprecatedAnnotation = (deprecatedClass as WithExtraProperties<out Documentable>).deprecatedAnnotation + assertNotNull(deprecatedAnnotation) + + assertTrue(deprecatedAnnotation.isDeprecated()) + assertEquals("java.lang", deprecatedAnnotation.dri.packageName) + assertEquals("Deprecated", deprecatedAnnotation.dri.classNames) + } + } + } + + @Test + fun `should change deprecated header if marked for removal`() { + testInline( + """ + |/src/main/kotlin/deprecated/DeprecatedJavaClass.java + |package deprecated + | + |/** + | * Average function description + | */ + |@Deprecated(forRemoval = true) + |public class DeprecatedJavaClass {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val deprecatedJavaClass = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "DeprecatedJavaClass" } as ContentPage + + deprecatedJavaClass.content.assertNode { + group { + header(1) { +"DeprecatedJavaClass" } + platformHinted { + skipAllNotMatching() + group { + header(3) { + +"Deprecated (for removal)" + } + } + group { pWrapped("Average function description") } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `should add footnote for 'since' param`() { + testInline( + """ + |/src/main/kotlin/deprecated/DeprecatedJavaClass.java + |package deprecated + | + |/** + | * Average function description + | */ + |@Deprecated(since = "11") + |public class DeprecatedJavaClass {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val deprecatedJavaClass = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "DeprecatedJavaClass" } as ContentPage + + deprecatedJavaClass.content.assertNode { + group { + header(1) { +"DeprecatedJavaClass" } + platformHinted { + skipAllNotMatching() + group { + header(3) { + +"Deprecated" + } + group { + check { assertEquals(ContentStyle.Footnote, this.style.firstOrNull()) } + +"Since version 11" + } + } + group { pWrapped("Average function description") } + } + } + skipAllNotMatching() + } + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/KotlinDeprecatedTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/KotlinDeprecatedTest.kt new file mode 100644 index 00000000..7612aff8 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/KotlinDeprecatedTest.kt @@ -0,0 +1,401 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.annotations + +import matchers.content.* +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.base.transformers.documentables.deprecatedAnnotation +import org.jetbrains.dokka.base.transformers.documentables.isDeprecated +import org.jetbrains.dokka.model.Documentable +import org.jetbrains.dokka.model.properties.WithExtraProperties +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.ContentStyle +import utils.ParamAttributes +import utils.bareSignature +import utils.pWrapped +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + + +class KotlinDeprecatedTest : BaseAbstractTest() { + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOfNotNull(jvmStdlibPath) + analysisPlatform = "jvm" + } + } + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `should assert util functions for deprecation`() { + testInline( + """ + |/src/main/kotlin/kotlin/KotlinFile.kt + |package deprecated + | + |@Deprecated( + | message = "Fancy message" + |) + |fun simpleFunction() {} + """.trimIndent(), + testConfiguration + ) { + documentablesTransformationStage = { module -> + val deprecatedFunction = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "simpleFunction" } + + val isDeprecated = (deprecatedFunction as WithExtraProperties<out Documentable>).isDeprecated() + assertTrue(isDeprecated) + + val deprecatedAnnotation = (deprecatedFunction as WithExtraProperties<out Documentable>).deprecatedAnnotation + assertNotNull(deprecatedAnnotation) + + assertTrue(deprecatedAnnotation.isDeprecated()) + assertEquals("kotlin", deprecatedAnnotation.dri.packageName) + assertEquals("Deprecated", deprecatedAnnotation.dri.classNames) + } + } + } + + @Test + fun `should change header if deprecation level is not default`() { + testInline( + """ + |/src/main/kotlin/kotlin/DeprecatedKotlin.kt + |package deprecated + | + |/** + | * Average function description + | */ + |@Deprecated( + | message = "Reason for deprecation bla bla", + | level = DeprecationLevel.ERROR + |) + |fun oldLegacyFunction(typedParam: SomeOldType, someLiteral: String): String {} + | + |fun newShinyFunction(typedParam: SomeOldType, someLiteral: String, newTypedParam: SomeNewType): String {} + |class SomeOldType {} + |class SomeNewType {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionWithDeprecatedFunction = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "oldLegacyFunction" } as ContentPage + + functionWithDeprecatedFunction.content.assertNode { + group { + header(1) { +"oldLegacyFunction" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "oldLegacyFunction", + returnType = "String", + params = arrayOf( + "typedParam" to ParamAttributes(emptyMap(), emptySet(), "SomeOldType"), + "someLiteral" to ParamAttributes(emptyMap(), emptySet(), "String"), + ) + ) + } + after { + group { + header(3) { + +"Deprecated (with error)" + } + p { + +"Reason for deprecation bla bla" + } + } + group { pWrapped("Average function description") } + } + } + } + } + } + } + } + + @Test + fun `should display repalceWith param with imports as code blocks`() { + testInline( + """ + |/src/main/kotlin/kotlin/DeprecatedKotlin.kt + |package deprecated + | + |/** + | * Average function description + | */ + |@Deprecated( + | message = "Reason for deprecation bla bla", + | replaceWith = ReplaceWith( + | "newShinyFunction(typedParam, someLiteral, SomeNewType())", + | imports = [ + | "com.example.dokka.debug.newShinyFunction", + | "com.example.dokka.debug.SomeOldType", + | "com.example.dokka.debug.SomeNewType", + | ] + | ), + |) + |fun oldLegacyFunction(typedParam: SomeOldType, someLiteral: String): String {} + | + |fun newShinyFunction(typedParam: SomeOldType, someLiteral: String, newTypedParam: SomeNewType): String {} + |class SomeOldType {} + |class SomeNewType {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionWithDeprecatedFunction = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "oldLegacyFunction" } as ContentPage + + functionWithDeprecatedFunction.content.assertNode { + group { + header(1) { +"oldLegacyFunction" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "oldLegacyFunction", + returnType = "String", + params = arrayOf( + "typedParam" to ParamAttributes(emptyMap(), emptySet(), "SomeOldType"), + "someLiteral" to ParamAttributes(emptyMap(), emptySet(), "String"), + ) + ) + } + after { + group { + header(3) { + +"Deprecated" + } + p { + +"Reason for deprecation bla bla" + } + + header(4) { + +"Replace with" + } + codeBlock { + +"import com.example.dokka.debug.newShinyFunction" + br() + +"import com.example.dokka.debug.SomeOldType" + br() + +"import com.example.dokka.debug.SomeNewType" + br() + } + codeBlock { + +"newShinyFunction(typedParam, someLiteral, SomeNewType())" + } + } + group { pWrapped("Average function description") } + } + } + } + } + } + } + } + + @Test + fun `should add footnote for DeprecatedSinceKotlin annotation`() { + testInline( + """ + |/src/main/kotlin/kotlin/DeprecatedKotlin.kt + |package deprecated + | + |/** + | * Average function description + | */ + |@DeprecatedSinceKotlin( + | warningSince = "1.4", + | errorSince = "1.5", + | hiddenSince = "1.6" + |) + |@Deprecated( + | message = "Deprecation reason bla bla" + |) + |fun oldLegacyFunction(typedParam: SomeOldType, someLiteral: String): String {} + | + |fun newShinyFunction(typedParam: SomeOldType, someLiteral: String, newTypedParam: SomeNewType): String {} + |class SomeOldType {} + |class SomeNewType {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionWithDeprecatedFunction = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "oldLegacyFunction" } as ContentPage + + functionWithDeprecatedFunction.content.assertNode { + group { + header(1) { +"oldLegacyFunction" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "oldLegacyFunction", + returnType = "String", + params = arrayOf( + "typedParam" to ParamAttributes(emptyMap(), emptySet(), "SomeOldType"), + "someLiteral" to ParamAttributes(emptyMap(), emptySet(), "String"), + ) + ) + } + after { + group { + header(3) { + +"Deprecated" + } + group { + check { assertEquals(ContentStyle.Footnote, this.style.firstOrNull()) } + p { + +"Warning since 1.4" + } + p { + +"Error since 1.5" + } + p { + +"Hidden since 1.6" + } + } + p { + +"Deprecation reason bla bla" + } + } + group { pWrapped("Average function description") } + } + } + } + } + } + } + } + + @Test + fun `should generate deprecation block with all parameters present and long description`() { + testInline( + """ + |/src/main/kotlin/kotlin/DeprecatedKotlin.kt + |package deprecated + | + |/** + | * Average function description + | */ + |@Deprecated( + | message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vel vulputate risus. " + + | "Etiam dictum odio vel vulputate auctor.Nulla facilisi. Duis ullamcorper ullamcorper lectus " + + | "nec rutrum. Quisque eu risus eu purus bibendum ultricies. Maecenas tincidunt dui in sodales " + + | "faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id sem felis. " + + | "Praesent et libero lacinia, egestas libero in, ultrices lectus. Suspendisse eget volutpat " + + | "velit. Phasellus laoreet mi eu egestas mattis.", + | replaceWith = ReplaceWith( + | "newShinyFunction(typedParam, someLiteral, SomeNewType())", + | imports = [ + | "com.example.dokka.debug.newShinyFunction", + | "com.example.dokka.debug.SomeOldType", + | "com.example.dokka.debug.SomeNewType", + | ] + | ), + | level = DeprecationLevel.ERROR + |) + |fun oldLegacyFunction(typedParam: SomeOldType, someLiteral: String): String {} + | + |fun newShinyFunction(typedParam: SomeOldType, someLiteral: String, newTypedParam: SomeNewType): String {} + |class SomeOldType {} + |class SomeNewType {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionWithDeprecatedFunction = module.children + .single { it.name == "deprecated" }.children + .single { it.name == "oldLegacyFunction" } as ContentPage + + functionWithDeprecatedFunction.content.assertNode { + group { + header(1) { +"oldLegacyFunction" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "oldLegacyFunction", + returnType = "String", + params = arrayOf( + "typedParam" to ParamAttributes(emptyMap(), emptySet(), "SomeOldType"), + "someLiteral" to ParamAttributes(emptyMap(), emptySet(), "String"), + ) + ) + } + after { + group { + header(3) { + +"Deprecated (with error)" + } + p { + +("Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + + "Maecenas vel vulputate risus. Etiam dictum odio vel " + + "vulputate auctor.Nulla facilisi. Duis ullamcorper " + + "ullamcorper lectus nec rutrum. Quisque eu risus eu " + + "purus bibendum ultricies. Maecenas tincidunt dui in sodales faucibus. " + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + + "Proin id sem felis. Praesent et libero lacinia, egestas " + + "libero in, ultrices lectus. Suspendisse eget volutpat velit. " + + "Phasellus laoreet mi eu egestas mattis.") + } + header(4) { + +"Replace with" + } + codeBlock { + +"import com.example.dokka.debug.newShinyFunction" + br() + +"import com.example.dokka.debug.SomeOldType" + br() + +"import com.example.dokka.debug.SomeNewType" + br() + } + codeBlock { + +"newShinyFunction(typedParam, someLiteral, SomeNewType())" + } + } + group { pWrapped("Average function description") } + } + } + } + } + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/SinceKotlinTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/SinceKotlinTest.kt new file mode 100644 index 00000000..6ee95bbd --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/annotations/SinceKotlinTest.kt @@ -0,0 +1,350 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.annotations + +import matchers.content.* +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.transformers.pages.annotations.SinceKotlinTransformer +import org.jetbrains.dokka.base.transformers.pages.annotations.SinceKotlinVersion +import org.jetbrains.dokka.model.DFunction +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.model.doc.CustomTagWrapper +import org.jetbrains.dokka.model.doc.Text +import org.jetbrains.dokka.pages.ContentPage +import signatures.AbstractRenderingTest +import utils.* +import kotlin.test.* + + +class SinceKotlinTest : AbstractRenderingTest() { + + val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOfNotNull(jvmStdlibPath) + analysisPlatform = "jvm" + } + } + } + + @BeforeTest + fun setSystemProperty() { + System.setProperty(SinceKotlinTransformer.SHOULD_DISPLAY_SINCE_KOTLIN_SYS_PROP, "true") + } + @AfterTest + fun clearSystemProperty() { + System.clearProperty(SinceKotlinTransformer.SHOULD_DISPLAY_SINCE_KOTLIN_SYS_PROP) + } + + @Test + fun versionsComparing() { + assertTrue(SinceKotlinVersion("1.0").compareTo(SinceKotlinVersion("1.0")) == 0) + assertTrue(SinceKotlinVersion("1.0.0").compareTo(SinceKotlinVersion("1")) == 0) + assertTrue(SinceKotlinVersion("1.0") >= SinceKotlinVersion("1.0")) + assertTrue(SinceKotlinVersion("1.1") > SinceKotlinVersion("1")) + assertTrue(SinceKotlinVersion("1.0") < SinceKotlinVersion("2.0")) + assertTrue(SinceKotlinVersion("1.0") < SinceKotlinVersion("2.2")) + } + + @Test + fun `rendered SinceKotlin custom tag for typealias, extensions, functions, properties`() { + val writerPlugin = TestOutputWriterPlugin() + + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@SinceKotlin("1.5") + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |@SinceKotlin("1.5") + |fun String.extension(abc: String): String { + | return "My precious " + abc + |} + |@SinceKotlin("1.5") + |typealias Str = String + |@SinceKotlin("1.5") + |val str = "str" + """.trimIndent(), + testConfiguration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.renderedContent("root/test/index.html") + assertEquals(4, content.getElementsContainingOwnText("Since Kotlin").count()) + } + } + } + + @Test + fun `should propagate SinceKotlin`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@SinceKotlin("1.5") + |class A { + | fun ring(abc: String): String { + | return "My precious " + abc + | } + |} + """.trimIndent(), testConfiguration + ) { + documentablesTransformationStage = { module -> + @Suppress("UNCHECKED_CAST") val funcs = module.children.single { it.name == "test" } + .children.single { it.name == "A" } + .children.filter { it.name == "ring" && it is DFunction } as List<DFunction> + with(funcs) { + val sinceKotlin = mapOf( + Platform.jvm to SinceKotlinVersion("1.5"), + ) + + for(i in sinceKotlin) { + val tag = + find { it.sourceSets.first().analysisPlatform == i.key }?.documentation?.values?.first() + ?.dfs { it is CustomTagWrapper && it.name == "Since Kotlin" } + .assertNotNull("SinceKotlin[${i.key}]") + assertEquals((tag.children.first() as Text).body, i.value.toString()) + } + } + } + } + } + + @Test + fun `mpp fun without SinceKotlin annotation`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/jvm/") + analysisPlatform = "jvm" + } + sourceSet { + sourceRoots = listOf("src/native/") + analysisPlatform = "native" + name = "native" + } + sourceSet { + sourceRoots = listOf("src/common/") + analysisPlatform = "common" + name = "common" + } + sourceSet { + sourceRoots = listOf("src/js/") + analysisPlatform = "js" + name = "js" + } + sourceSet { + sourceRoots = listOf("src/wasm/") + analysisPlatform = "wasm" + name = "wasm" + } + } + } + testInline( + """ + |/src/jvm/kotlin/test/source.kt + |package test + | + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |/src/native/kotlin/test/source.kt + |package test + | + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |/src/common/kotlin/test/source.kt + |package test + | + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |/src/js/kotlin/test/source.kt + |package test + | + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |/src/wasm/kotlin/test/source.kt + |package test + | + |fun ring(abc: String): String { + | return "My precious " + abc + |} + """.trimIndent(), configuration + ) { + documentablesTransformationStage = { module -> + @Suppress("UNCHECKED_CAST") val funcs = module.children.single { it.name == "test" } + .children.filter { it.name == "ring" && it is DFunction } as List<DFunction> + with(funcs) { + val sinceKotlin = mapOf( + Platform.common to SinceKotlinVersion("1.0"), + Platform.jvm to SinceKotlinVersion("1.0"), + Platform.js to SinceKotlinVersion("1.1"), + Platform.native to SinceKotlinVersion("1.3"), + Platform.wasm to SinceKotlinVersion("1.8"), + ) + + for(i in sinceKotlin) { + val tag = + find { it.sourceSets.first().analysisPlatform == i.key }?.documentation?.values?.first() + ?.dfs { it is CustomTagWrapper && it.name == "Since Kotlin" } + .assertNotNull("SinceKotlin[${i.key}]") + assertEquals((tag.children.first() as Text).body, i.value.toString()) + } + } + } + } + } + + @Test + fun `mpp fun with SinceKotlin annotation`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/jvm/") + classpath = listOfNotNull(jvmStdlibPath) + analysisPlatform = "jvm" + } + sourceSet { + sourceRoots = listOf("src/native/") + analysisPlatform = "native" + name = "native" + } + sourceSet { + sourceRoots = listOf("src/common/") + classpath = listOfNotNull(commonStdlibPath) + analysisPlatform = "common" + name = "common" + } + sourceSet { + sourceRoots = listOf("src/js/") + classpath = listOfNotNull(jsStdlibPath) + analysisPlatform = "js" + name = "js" + } + sourceSet { + sourceRoots = listOf("src/wasm/") + analysisPlatform = "wasm" + name = "wasm" + } + } + } + testInline( + """ + |/src/jvm/kotlin/test/source.kt + |package test + | + |/** dssdd */ + |@SinceKotlin("1.3") + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |/src/native/kotlin/test/source.kt + |package test + | + |/** dssdd */ + |@SinceKotlin("1.3") + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |/src/common/kotlin/test/source.kt + |package test + | + |/** dssdd */ + |@SinceKotlin("1.3") + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |/src/js/kotlin/test/source.kt + |package test + | + |/** dssdd */ + |@SinceKotlin("1.3") + |fun ring(abc: String): String { + | return "My precious " + abc + |} + |/src/wasm/kotlin/test/source.kt + |package test + | + |/** dssdd */ + |@SinceKotlin("1.3") + |fun ring(abc: String): String { + | return "My precious " + abc + |} + """.trimIndent(), configuration + ) { + documentablesTransformationStage = { module -> + @Suppress("UNCHECKED_CAST") val funcs = module.children.single { it.name == "test" } + .children.filter { it.name == "ring" && it is DFunction } as List<DFunction> + with(funcs) { + val sinceKotlin = mapOf( + Platform.common to SinceKotlinVersion("1.3"), + Platform.jvm to SinceKotlinVersion("1.3"), + Platform.js to SinceKotlinVersion("1.3"), + Platform.native to SinceKotlinVersion("1.3"), + Platform.wasm to SinceKotlinVersion("1.8"), + ) + + for(i in sinceKotlin) { + val tag = + find { it.sourceSets.first().analysisPlatform == i.key }?.documentation?.values?.first() + ?.dfs { it is CustomTagWrapper && it.name == "Since Kotlin" } + .assertNotNull("SinceKotlin[${i.key}]") + assertEquals(i.value.toString(), (tag.children.first() as Text).body , "Platform ${i.key}") + } + } + } + } + } + + @Test + fun `should do not render since kotlin tag when flag is unset`() { + clearSystemProperty() + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@SinceKotlin("1.3") + |fun ring(abc: String): String { + | return "My precious " + abc + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "ring" } as ContentPage + page.content.assertNode { + group { + header(1) { +"ring" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "ring", + "String", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + + } + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/exceptions/ContentForExceptions.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/exceptions/ContentForExceptions.kt new file mode 100644 index 00000000..22becb93 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/exceptions/ContentForExceptions.kt @@ -0,0 +1,439 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.exceptions + +import matchers.content.* +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.PluginConfigurationImpl +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.DisplaySourceSet +import utils.* +import kotlin.test.Test +import kotlin.test.assertEquals + +class ContentForExceptions : BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOfNotNull(jvmStdlibPath) + analysisPlatform = "jvm" + } + } + } + + private val mppTestConfiguration = dokkaConfiguration { + moduleName = "example" + sourceSets { + val common = sourceSet { + name = "common" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt") + classpath = listOfNotNull(commonStdlibPath) + } + sourceSet { + name = "jvm" + displayName = "jvm" + analysisPlatform = "jvm" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt") + classpath = listOfNotNull(jvmStdlibPath) + } + sourceSet { + name = "linuxX64" + displayName = "linuxX64" + analysisPlatform = "native" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/linuxX64Main/kotlin/pageMerger/Test.kt") + } + } + pluginsConfigurations.add( + PluginConfigurationImpl( + DokkaBase::class.qualifiedName!!, + DokkaConfiguration.SerializationFormat.JSON, + """{ "mergeImplicitExpectActualDeclarations": true }""", + ) + ) + } + + @OnlyDescriptors("Fixed in 1.9.20 (IMPORT STAR)") + @Test + fun `function with navigatable thrown exception`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |/** + |* @throws Exception + |*/ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + header(4) { +"Throws" } + table { + group { + group { + link { +"Exception" } + } + } + } + } + } + } + } + } + } + } + + @Test + fun `function with non-navigatable thrown exception`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |/** + |* @throws UnavailableException + |*/ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + header(4) { +"Throws" } + table { + group { + group { + +"UnavailableException" + } + } + } + } + } + } + } + } + } + } + + @Test + fun `multiplatofrm class with throws`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws CommonException + |*/ + |expect open class Parent + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws JvmException + |*/ + |actual open class Parent + | + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws LinuxException + |*/ + |actual open class Parent + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"expect open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + header(4) { +"Throws" } + table { + group { + group { + +"CommonException" + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "common", + this.sourceSets.first().name + ) + } + } + group { + group { + +"JvmException" + } + check { + sourceSets.assertSourceSet("jvm") + } + } + group { + group { + +"LinuxException" + } + check { + sourceSets.assertSourceSet("linuxX64") + } + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `multiplatofrm class with throws in few platforms`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws CommonException + |*/ + |expect open class Parent + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws JvmException + |*/ + |actual open class Parent + | + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual open class Parent + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"expect open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + header(4) { +"Throws" } + table { + group { + group { + +"CommonException" + } + check { + sourceSets.assertSourceSet("common") + } + } + group { + group { + +"JvmException" + } + check { + sourceSets.assertSourceSet("jvm") + } + } + check { + assertEquals(2, sourceSets.size) + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `throws in merged functions`() { + testInline( + """ + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws LinuxException + |*/ + |fun function() { + | println() + |} + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @throws JvmException + |*/ + |fun function() { + | println() + |} + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + ) + } + after { + header(4) { +"Throws" } + table { + group { + group { + +"JvmException" + } + } + check { + sourceSets.assertSourceSet("jvm") + } + } + } + check { + sourceSets.assertSourceSet("jvm") + } + } + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + ) + } + after { + header(4) { +"Throws" } + table { + group { + group { + +"LinuxException" + } + } + } + } + check { + sourceSets.assertSourceSet("linuxX64") + } + } + } + } + } + } + } +} + +private fun Set<DisplaySourceSet>.assertSourceSet(expectedName: String) { + assertEquals(1, this.size) + assertEquals(expectedName, this.first().name) +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/functions/ContentForBriefTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/functions/ContentForBriefTest.kt new file mode 100644 index 00000000..d93a6c27 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/functions/ContentForBriefTest.kt @@ -0,0 +1,388 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.functions + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.TypeConstructor +import org.jetbrains.dokka.model.DClass +import org.jetbrains.dokka.model.DPackage +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.pages.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + + +class ContentForBriefTest : BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + private val codeWithSecondaryAndPrimaryConstructorsDocumented = + """ + |/src/main/kotlin/test/source.kt + |package test + | + |/** + | * Dummy text. + | * + | * @constructor constructor docs + | * @param exampleParameter dummy parameter. + | */ + |class Example(val exampleParameter: Int) { + | + | /** + | * secondary constructor + | * @param param1 param1 docs + | */ + | constructor(param1: String) : this(1) + |} + """.trimIndent() + + private val codeWithDocumentedParameter = + """ + |/src/main/kotlin/test/source.kt + |package test + | + |/** + | * Dummy text. + | * + | * @param exampleParameter dummy parameter. + | */ + |class Example(val exampleParameter: Int) { + |} + """.trimIndent() + + + @Test + fun `primary constructor should not inherit docs from its parameter`() { + testInline(codeWithSecondaryAndPrimaryConstructorsDocumented, testConfiguration) { + pagesTransformationStage = { module -> + val classPage = module.findClassPage("Example") + + val constructorsWithBriefs = classPage.findConstructorsWithBriefs() + val constructorDocs = constructorsWithBriefs.findConstructorDocs { + it.callable?.params?.first() == TypeConstructor("kotlin.Int", emptyList()) + } + + assertEquals("constructor docs", constructorDocs.text) + } + } + } + + @Test + fun `secondary constructor should not inherit docs from its parameter`() { + testInline(codeWithSecondaryAndPrimaryConstructorsDocumented, testConfiguration) { + pagesTransformationStage = { module -> + val classPage = module.findClassPage("Example") + + val constructorsWithBriefs = classPage.findConstructorsWithBriefs() + val constructorDocs = constructorsWithBriefs.findConstructorDocs { + it.callable?.params?.first() == TypeConstructor("kotlin.String", emptyList()) + } + + assertEquals("secondary constructor", constructorDocs.text) + } + } + } + + /** + * All constructors are merged in one block (like overloaded functions). + * That leads to the structure where content block (`constructorsWithBriefs`) consist of plain list + * of constructors and briefs. In that list constructor is above, brief is below. + */ + private fun ContentPage.findConstructorsWithBriefs(): List<ContentNode> { + val constructorsTable = this.content.dfs { + it is ContentTable && it.dci.kind == ContentKind.Constructors + } as ContentTable + + val constructorsWithBriefs = constructorsTable.dfs { + it is ContentGroup && it.dci.kind == ContentKind.SourceSetDependentHint + }?.children + assertNotNull(constructorsWithBriefs, "Content node with constructors and briefs is not found") + + return constructorsWithBriefs + } + + private fun List<ContentNode>.findConstructorDocs(constructorMatcher: (DRI) -> Boolean): ContentText { + val constructorIndex = this.indexOfFirst { constructorMatcher(it.dci.dri.first()) } + return this[constructorIndex + 1] // expect that the relevant comment is below the constructor + .dfs { it is ContentText && it.dci.kind == ContentKind.Comment } as ContentText + } + + @Test + fun `primary constructor should not inherit docs from its parameter when no specific docs are provided`() { + testInline(codeWithDocumentedParameter, testConfiguration) { + pagesTransformationStage = { module -> + val classPage = module.findClassPage("Example") + val constructorsTable = + classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Constructors } as ContentTable + + assertEquals(1, constructorsTable.children.size) + val primary = constructorsTable.children.first() + val primaryConstructorDocs = primary.dfs { it is ContentText && it.dci.kind == ContentKind.Comment } + + assertNull(primaryConstructorDocs, "Expected no primary constructor docs to be present") + } + } + } + + @Test + fun `brief should work for typealias`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |/** + |* This is an example <!-- not visible --> of html + |* + |* This is definitely not a brief + |*/ + |typealias A = Int + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionBriefDocs = module.singleTypeAliasesDescription("test") + + assertEquals( + "This is an example <!-- not visible --> of html", + functionBriefDocs.children.joinToString("") { (it as ContentText).text }) + } + } + } + + @Test + fun `brief for functions should work with html`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |class Example(val exampleParameter: Int) { + | /** + | * This is an example <!-- not visible --> of html + | * + | * This is definitely not a brief + | */ + | fun test(): String = "TODO" + |} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionBriefDocs = module.singleFunctionDescription("Example") + + assertEquals( + "This is an example <!-- not visible --> of html", + functionBriefDocs.children.joinToString("") { (it as ContentText).text }) + } + } + } + + @Test + fun `brief for functions should work with ie`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |class Example(val exampleParameter: Int) { + | /** + | * The user token, i.e. "Bearer xyz". Throw an exception if not available. + | * + | * This is definitely not a brief + | */ + | fun test(): String = "TODO" + |} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionBriefDocs = module.singleFunctionDescription("Example") + + assertEquals( + "The user token, i.e. \"Bearer xyz\". Throw an exception if not available.", + functionBriefDocs.children.joinToString("") { (it as ContentText).text }) + } + } + } + + @Test + fun `brief for functions should work with eg`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |class Example(val exampleParameter: Int) { + | /** + | * The user token, e.g. "Bearer xyz". Throw an exception if not available. + | * + | * This is definitely not a brief + | */ + | fun test(): String = "TODO" + |} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionBriefDocs = module.singleFunctionDescription("Example") + + assertEquals( + "The user token, e.g. \"Bearer xyz\". Throw an exception if not available.", + functionBriefDocs.children.joinToString("") { (it as ContentText).text }) + } + } + } + + @Test + fun `brief for functions should be first sentence for Java`() { + testInline( + """ + |/src/main/java/test/Example.java + |package test; + | + |public class Example { + | /** + | * The user token, or not. This is definitely not a brief in java + | */ + | public static String test() { + | return "TODO"; + | } + |} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionBriefDocs = module.singleFunctionDescription("Example") + + assertEquals( + "The user token, or not.", + functionBriefDocs.children.joinToString("") { (it as ContentText).text }) + } + } + } + + @Test + fun `brief for functions should work with ie for Java`() { + testInline( + """ + |/src/main/java/test/Example.java + |package test; + | + |public class Example { + | /** + | * The user token, e.g. "Bearer xyz". This is definitely not a brief in java + | */ + | public static String test() { + | return "TODO"; + | } + |} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionBriefDocs = module.singleFunctionDescription("Example") + + assertEquals( + "The user token, e.g. \"Bearer xyz\".", + functionBriefDocs.children.joinToString("") { (it as ContentText).text }) + } + } + } + + //Source: https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html#exampleresult + @Test + fun `brief for functions should work with html comment for Java`() { + testInline( + """ + |/src/main/java/test/Example.java + |package test; + | + |public class Example { + | /** + | * This is a simulation of Prof.<!-- --> Knuth's MIX computer. This is definitely not a brief in java + | */ + | public static String test() { + | return "TODO"; + | } + |} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionBriefDocs = module.singleFunctionDescription("Example") + + assertEquals( + "This is a simulation of Prof.<!-- --> Knuth's MIX computer.", + functionBriefDocs.children.joinToString("") { (it as ContentText).text }) + } + } + } + + @Test + fun `brief for functions should work with html comment at the end for Java`() { + testInline( + """ + |/src/main/java/test/Example.java + |package test; + | + |public class Example { + | /** + | * This is a simulation of Prof.<!-- --> Knuth's MIX computer. This is definitely not a brief in java <!-- --> + | */ + | public static String test() { + | return "TODO"; + | } + |} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val functionBriefDocs = module.singleFunctionDescription("Example") + + assertEquals( + "This is a simulation of Prof.<!-- --> Knuth's MIX computer.", + functionBriefDocs.children.joinToString("") { (it as ContentText).text }) + } + } + } + + private fun RootPageNode.findClassPage(className: String): ContentPage { + return this.dfs { + it.name == className && (it as WithDocumentables).documentables.firstOrNull() is DClass + } as ContentPage + } + + private fun RootPageNode.singleFunctionDescription(className: String): ContentGroup { + val classPage = + dfs { it.name == className && (it as WithDocumentables).documentables.firstOrNull() is DClass } as ContentPage + val functionsTable = + classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Functions } as ContentTable + + assertEquals(1, functionsTable.children.size) + val function = functionsTable.children.first() + return function.dfs { it is ContentGroup && it.dci.kind == ContentKind.Comment && it.children.all { it is ContentText } } as ContentGroup + } + private fun RootPageNode.singleTypeAliasesDescription(packageName: String): ContentGroup { + val packagePage = + dfs { it.name == packageName && (it as WithDocumentables).documentables.firstOrNull() is DPackage } as ContentPage + val contentTable = + packagePage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Classlikes } as ContentTable + + assertEquals(1, contentTable.children.size) + val row = contentTable.children.first() + return row.dfs { it is ContentGroup && it.dci.kind == ContentKind.Comment && it.children.all { it is ContentText } } as ContentGroup + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/functions/ContentForConstructors.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/functions/ContentForConstructors.kt new file mode 100644 index 00000000..d1ed93dc --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/functions/ContentForConstructors.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.functions + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.DClass +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.pages.* +import utils.assertContains +import utils.assertNotNull +import kotlin.test.Test +import kotlin.test.assertEquals + +class ContentForConstructors : BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + @Test + fun `constructor name should have RowTitle style`() { + testInline(""" + |/src/main/kotlin/test/source.kt + |package test + | + |/** + | * Dummy text. + | */ + |class Example(val exampleParameter: Int) { + |} + """.trimIndent(), testConfiguration) { + pagesTransformationStage = { module -> + val classPage = + module.dfs { it.name == "Example" && (it as WithDocumentables).documentables.firstOrNull() is DClass } as ContentPage + val constructorsTable = + classPage.content.dfs { it is ContentTable && it.dci.kind == ContentKind.Constructors } as ContentTable + + assertEquals(1, constructorsTable.children.size) + val primary = constructorsTable.children.first() + val constructorName = + primary.dfs { (it as? ContentText)?.text == "Example" }.assertNotNull("constructorName") + + assertContains(constructorName.style, ContentStyle.RowTitle) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/inheritors/ContentForInheritorsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/inheritors/ContentForInheritorsTest.kt new file mode 100644 index 00000000..245592cc --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/inheritors/ContentForInheritorsTest.kt @@ -0,0 +1,499 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.inheritors + +import matchers.content.* +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.PluginConfigurationImpl +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import utils.OnlyDescriptors +import utils.classSignature +import utils.findTestType +import kotlin.test.Test +import kotlin.test.assertEquals + +class ContentForInheritorsTest : BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + private val mppTestConfiguration = dokkaConfiguration { + moduleName = "example" + sourceSets { + val common = sourceSet { + name = "common" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "jvm" + displayName = "jvm" + analysisPlatform = "jvm" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "linuxX64" + displayName = "linuxX64" + analysisPlatform = "native" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/linuxX64Main/kotlin/pageMerger/Test.kt") + } + } + pluginsConfigurations.add( + PluginConfigurationImpl( + DokkaBase::class.qualifiedName!!, + DokkaConfiguration.SerializationFormat.JSON, + """{ "mergeImplicitExpectActualDeclarations": true }""", + ) + ) + } + + + //Case from skiko library + private val mppTestConfigurationSharedAsPlatform = dokkaConfiguration { + moduleName = "example" + sourceSets { + val common = sourceSet { + name = "common" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt") + } + val jvm = sourceSet { + name = "jvm" + displayName = "jvm" + analysisPlatform = "jvm" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "android" + displayName = "android" + analysisPlatform = "jvm" + dependentSourceSets = setOf(jvm.value.sourceSetID) + sourceRoots = listOf("src/androidMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "awt" + displayName = "awt" + analysisPlatform = "jvm" + dependentSourceSets = setOf(jvm.value.sourceSetID) + sourceRoots = listOf("src/awtMain/kotlin/pageMerger/Test.kt") + } + + } + } + + @Test + fun `class with one inheritor has table in description`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |class Parent + | + |class Foo : Parent() + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Parent" + ) + header(4) { +"Inheritors" } + table { + group { + link { +"Foo" } + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @OnlyDescriptors("Order of inheritors is different in K2") + @Test + fun `interface with few inheritors has table in description`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |interface Parent + | + |class Foo : Parent() + |class Bar : Parent() + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"interface " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"Foo" } + } + group { + link { +"Bar" } + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `inherit from one of multiplatoforms actuals`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |expect open class Parent + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual open class Parent + | + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual open class Parent + |class Child: Parent() + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"expect open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"Child" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "linuxX64", + this.sourceSets.first().name + ) + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `inherit from class in common code`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |open class Parent + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class Child : Parent() + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"open class " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"Child" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "common", + this.sourceSets.first().name + ) + } + } + } + } + skipAllNotMatching() + } + } + } + } + + + @Test + fun `inheritors from merged classes`() { + testInline( + """ + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |open class Parent + |class LChild : Parent() + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |open class Parent + |class JChild : Parent() + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"open class " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"JChild" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "jvm", + this.sourceSets.first().name + ) + } + } + group { + +"open class " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"LChild" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "linuxX64", + this.sourceSets.first().name + ) + } + } + } + } + skipAllNotMatching() + } + } + } + } + + + @Test + fun `merged inheritors from merged classes`() { + testInline( + """ + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |open class Parent + |class Child : Parent() + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |open class Parent + |class Child : Parent() + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"open class " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"Child" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "jvm", + this.sourceSets.first().name + ) + } + } + group { + +"open class " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"Child" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "linuxX64", + this.sourceSets.first().name + ) + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `parent in shared source set that analyse as platform`() { + testInline( + """ + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |interface Parent + | + |/src/androidMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class Child : Parent + | + |/src/awtMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class AwtChild : Parent + |class Child : Parent + | + """.trimMargin(), + mppTestConfigurationSharedAsPlatform + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"interface " + link { + +"Parent" + } + } + header(4) { +"Inheritors" } + table { + group { + link { +"Child" } + } + group { + link { +"AwtChild" } + } + check { + assertEquals(1, sourceSets.size) + assertEquals( + "jvm", + this.sourceSets.first().name + ) + } + } + } + } + skipAllNotMatching() + } + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/params/ContentForParamsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/params/ContentForParamsTest.kt new file mode 100644 index 00000000..d0c6ac9d --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/params/ContentForParamsTest.kt @@ -0,0 +1,1529 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.params + +import matchers.content.* +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.DFunction +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.dokka.model.doc.Param +import org.jetbrains.dokka.model.doc.Text +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.utilities.firstIsInstanceOrNull +import utils.* +import kotlin.test.Test +import kotlin.test.assertEquals + +class ContentForParamsTest : BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOfNotNull(jvmStdlibPath) + analysisPlatform = "jvm" + } + } + } + + @Test + fun `undocumented function`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), "", "", emptySet(), "function", null, "abc" to ParamAttributes( + emptyMap(), + emptySet(), + "String" + ) + ) + } + } + } + } + } + } + } + + @Test + fun `undocumented parameter`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + group { pWrapped("comment to function") } + } + } + } + } + } + } + } + + @Test + fun `undocumented parameter and other tags without function comment`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @author Kordyjan + | * @author Woolfy + | * @since 0.11 + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + unnamedTag("Author") { + comment { + +"Kordyjan" + } + comment { + +"Woolfy" + } + } + unnamedTag("Since") { comment { +"0.11" } } + } + } + } + } + } + } + } + + @Test + fun `multiple authors`() { + testInline( + """ + |/src/main/java/sample/DocGenProcessor.java + |package sample; + |/** + | * Annotation processor which visits all classes. + | * + | * @author googler1@google.com (Googler 1) + | * @author googler2@google.com (Googler 2) + | */ + | public class DocGenProcessor { } + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val classPage = module.findTestType("sample", "DocGenProcessor") + classPage.content.assertNode { + group { + header { +"DocGenProcessor" } + platformHinted { + group { + skipAllNotMatching() //Signature + } + group { + group { + group { + +"Annotation processor which visits all classes." + } + } + } + group { + header(4) { +"Author" } + comment { +"googler1@google.com (Googler 1)" } + comment { +"googler2@google.com (Googler 2)" } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `author delimetered by space`() { + testInline( + """ + |/src/main/java/sample/DocGenProcessor.java + |package sample; + |/** + | * Annotation processor which visits all classes. + | * + | * @author Marcin Aman Senior + | */ + | public class DocGenProcessor { } + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val classPage = module.findTestType("sample", "DocGenProcessor") + classPage.content.assertNode { + group { + header { +"DocGenProcessor" } + platformHinted { + group { + skipAllNotMatching() //Signature + } + group { + group { + group { + +"Annotation processor which visits all classes." + } + } + } + group { + header(4) { +"Author" } + comment { +"Marcin Aman Senior" } + } + } + } + skipAllNotMatching() + } + } + } + } + + + @Test + fun `deprecated with multiple links inside`() { + testInline( + """ + |/src/main/java/sample/DocGenProcessor.java + |package sample; + |/** + | * Return the target fragment set by {@link #setTargetFragment} or {@link + | * #setTargetFragment}. + | * + | * @deprecated Instead of using a target fragment to pass results, the fragment requesting a + | * result should use + | * {@link java.util.HashMap#containsKey(java.lang.Object) FragmentManager#setFragmentResult(String, Bundle)} to deliver results to + | * {@link java.util.HashMap#containsKey(java.lang.Object) + | * FragmentResultListener} instances registered by other fragments via + | * {@link java.util.HashMap#containsKey(java.lang.Object) FragmentManager#setFragmentResultListener(String, LifecycleOwner, + | * FragmentResultListener)}. + | */ + | public class DocGenProcessor { + | public String setTargetFragment(){ + | return ""; + | } + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val classPage = + module.findTestType("sample", "DocGenProcessor") + classPage.content.assertNode { + group { + header { +"DocGenProcessor" } + platformHinted { + group { + skipAllNotMatching() //Signature + } + group { + comment { + +"Return the target fragment set by " + link { +"setTargetFragment" } + +" or " + link { +"setTargetFragment" } + +"." + } + } + group { + header(4) { +"Deprecated" } + comment { + +"Instead of using a target fragment to pass results, the fragment requesting a result should use " + link { +"FragmentManager#setFragmentResult(String, Bundle)" } + +" to deliver results to " + link { +"FragmentResultListener" } + +" instances registered by other fragments via " + link { +"FragmentManager#setFragmentResultListener(String, LifecycleOwner, FragmentResultListener)" } + +"." + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `deprecated with an html link in multiple lines`() { + testInline( + """ + |/src/main/java/sample/DocGenProcessor.java + |package sample; + |/** + | * @deprecated Use + | * <a href="https://developer.android.com/guide/navigation/navigation-swipe-view "> + | * TabLayout and ViewPager</a> instead. + | */ + | public class DocGenProcessor { } + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val classPage = + module.findTestType("sample", "DocGenProcessor") + classPage.content.assertNode { + group { + header { +"DocGenProcessor" } + platformHinted { + group { + skipAllNotMatching() //Signature + } + group { + header(4) { +"Deprecated" } + comment { + +"Use " + link { +"TabLayout and ViewPager" } + +" instead." + } + } + } + } + skipAllNotMatching() + } + } + } + } + + + @Test + fun `deprecated with an multiple inline links`() { + testInline( + """ + |/src/main/java/sample/DocGenProcessor.java + |package sample; + |/** + | * FragmentManagerNonConfig stores the retained instance fragments across + | * activity recreation events. + | * + | * <p>Apps should treat objects of this type as opaque, returned by + | * and passed to the state save and restore process for fragments in + | * {@link java.util.HashMap#containsKey(java.lang.Object) FragmentController#retainNestedNonConfig()} and + | * {@link java.util.HashMap#containsKey(java.lang.Object) FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p> + | * + | * @deprecated Have your {@link java.util.HashMap FragmentHostCallback} implement + | * {@link java.util.HashMap } to automatically retain the Fragment's + | * non configuration state. + | */ + | public class DocGenProcessor { } + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val classPage = + module.findTestType("sample", "DocGenProcessor") + classPage.content.assertNode { + group { + header { +"DocGenProcessor" } + platformHinted { + group { + skipAllNotMatching() //Signature + } + group { + comment { + group { + +"FragmentManagerNonConfig stores the retained instance fragments across activity recreation events. " + } + group { + +"Apps should treat objects of this type as opaque, returned by and passed to the state save and restore process for fragments in " + link { +"FragmentController#retainNestedNonConfig()" } + +" and " + link { +"FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)" } + +"." + } + } + } + group { + header(4) { +"Deprecated" } + comment { + +"Have your " + link { +"FragmentHostCallback" } + +" implement " + link { +"java.util.HashMap" } + +" to automatically retain the Fragment's non configuration state." + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `multiline throws with comment`() { + testInline( + """ + |/src/main/java/sample/DocGenProcessor.java + |package sample; + | public class DocGenProcessor { + | /** + | * a normal comment + | * + | * @throws java.lang.IllegalStateException if the Dialog has not yet been created (before + | * onCreateDialog) or has been destroyed (after onDestroyView). + | * @throws java.lang.RuntimeException when {@link java.util.HashMap#containsKey(java.lang.Object) Hash + | * Map} doesn't contain value. + | */ + | public static void sample(){ } + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val functionPage = + module.findTestType( + "sample", + "DocGenProcessor" + ).children.single { it.name == "sample" } as ContentPage + functionPage.content.assertNode { + group { + header(1) { +"sample" } + } + divergentGroup { + divergentInstance { + divergent { + skipAllNotMatching() //Signature + } + after { + group { pWrapped("a normal comment") } + header(4) { +"Throws" } + table { + group { + group { + link { +"IllegalStateException" } + } + comment { +"if the Dialog has not yet been created (before onCreateDialog) or has been destroyed (after onDestroyView)." } + } + group { + group { + link { +"RuntimeException" } + } + comment { + +"when " + link { +"Hash Map" } + +" doesn't contain value." + } + } + } + } + } + } + } + } + } + } + + @OnlyDescriptors("Fixed in 1.9.20 (IMPORT STAR)") + @Test + fun `multiline kotlin throws with comment`() { + testInline( + """ + |/src/main/kotlin/sample/sample.kt + |package sample; + | /** + | * a normal comment + | * + | * @throws java.lang.IllegalStateException if the Dialog has not yet been created (before + | * onCreateDialog) or has been destroyed (after onDestroyView). + | * @exception RuntimeException when [Hash Map][java.util.HashMap.containsKey] doesn't contain value. + | */ + | fun sample(){ } + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val functionPage = module.findTestType("sample", "sample") + functionPage.content.assertNode { + group { + header(1) { +"sample" } + } + divergentGroup { + divergentInstance { + divergent { + skipAllNotMatching() //Signature + } + after { + group { pWrapped("a normal comment") } + header(4) { +"Throws" } + table { + group { + group { + link { + check { + assertEquals( + "java.lang/IllegalStateException///PointingToDeclaration/", + (this as ContentDRILink).address.toString() + ) + } + +"IllegalStateException" + } + } + comment { +"if the Dialog has not yet been created (before onCreateDialog) or has been destroyed (after onDestroyView)." } + } + group { + group { + link { + check { + assertEquals( + "kotlin/RuntimeException///PointingToDeclaration/", + (this as ContentDRILink).address.toString() + ) + } + +"RuntimeException" + } + } + comment { + +"when " + link { +"Hash Map" } + +" doesn't contain value." + } + } + } + } + } + } + } + } + } + } + + @Test + fun `should display fully qualified throws name for unresolved class`() { + testInline( + """ + |/src/main/kotlin/sample/sample.kt + |package sample; + | /** + | * a normal comment + | * + | * @throws com.example.UnknownException description for non-resolved + | */ + | fun sample(){ } + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val functionPage = + module.findTestType("sample", "sample") + functionPage.content.assertNode { + group { + header(1) { +"sample" } + } + divergentGroup { + divergentInstance { + divergent { + skipAllNotMatching() //Signature + } + after { + group { pWrapped("a normal comment") } + header(4) { +"Throws" } + table { + group { + group { + +"com.example.UnknownException" + } + comment { +"description for non-resolved" } + } + } + } + } + } + } + } + } + } + + @Test + fun `multiline throws where exception is not in the same line as description`() { + testInline( + """ + |/src/main/java/sample/DocGenProcessor.java + |package sample; + | public class DocGenProcessor { + | /** + | * a normal comment + | * + | * @throws java.lang.IllegalStateException if the Dialog has not yet been created (before + | * onCreateDialog) or has been destroyed (after onDestroyView). + | * @throws java.lang.RuntimeException when + | * {@link java.util.HashMap#containsKey(java.lang.Object) Hash + | * Map} + | * doesn't contain value. + | */ + | public static void sample(){ } + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val functionPage = + module.findTestType( + "sample", + "DocGenProcessor" + ).children.single { it.name == "sample" } as ContentPage + functionPage.content.assertNode { + group { + header(1) { +"sample" } + } + divergentGroup { + divergentInstance { + divergent { + skipAllNotMatching() //Signature + } + after { + group { pWrapped("a normal comment") } + header(4) { +"Throws" } + table { + group { + group { + link { + check { + assertEquals( + "java.lang/IllegalStateException///PointingToDeclaration/", + (this as ContentDRILink).address.toString() + ) + } + +"IllegalStateException" + } + } + comment { +"if the Dialog has not yet been created (before onCreateDialog) or has been destroyed (after onDestroyView)." } + } + group { + group { + link { + check { + assertEquals( + "java.lang/RuntimeException///PointingToDeclaration/", + (this as ContentDRILink).address.toString() + ) + } + +"RuntimeException" + } + } + comment { + +"when " + link { +"Hash Map" } + +" doesn't contain value." + } + } + } + } + } + + } + } + } + } + } + + + + @Test + fun `documentation splitted in 2 using enters`() { + testInline( + """ + |/src/main/java/sample/DocGenProcessor.java + |package sample; + |/** + | * Listener for handling fragment results. + | * + | * This object should be passed to + | * {@link java.util.HashMap#containsKey(java.lang.Object) FragmentManager#setFragmentResultListener(String, LifecycleOwner, FragmentResultListener)} + | * and it will listen for results with the same key that are passed into + | * {@link java.util.HashMap#containsKey(java.lang.Object) FragmentManager#setFragmentResult(String, Bundle)}. + | * + | */ + | public class DocGenProcessor { } + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val classPage = + module.findTestType("sample", "DocGenProcessor") + classPage.content.assertNode { + group { + header { +"DocGenProcessor" } + platformHinted { + group { + skipAllNotMatching() //Signature + } + group { + comment { + +"Listener for handling fragment results. This object should be passed to " + link { +"FragmentManager#setFragmentResultListener(String, LifecycleOwner, FragmentResultListener)" } + +" and it will listen for results with the same key that are passed into " + link { +"FragmentManager#setFragmentResult(String, Bundle)" } + +"." + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `multiline return tag with param`() { + testInline( + """ + |/src/main/java/sample/DocGenProcessor.java + |package sample; + | public class DocGenProcessor { + | /** + | * a normal comment + | * + | * @param testParam Sample description for test param that has a type of {@link java.lang.String String} + | * @return empty string when + | * {@link java.util.HashMap#containsKey(java.lang.Object) Hash + | * Map} + | * doesn't contain value. + | */ + | public static String sample(String testParam){ + | return ""; + | } + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val functionPage = + module.findTestType( + "sample", + "DocGenProcessor" + ).children.single { it.name == "sample" } as ContentPage + functionPage.content.assertNode { + group { + header(1) { +"sample" } + } + divergentGroup { + divergentInstance { + divergent { + skipAllNotMatching() //Signature + } + after { + group { pWrapped("a normal comment") } + group { + header(4) { +"Return" } + comment { + +"empty string when " + link { +"Hash Map" } + +" doesn't contain value." + } + } + header(4) { +"Parameters" } + table { + group { + +"testParam" + comment { + +"Sample description for test param that has a type of " + link { +"String" } + } + } + } + } + } + } + } + } + } + } + + @Test + fun `return tag in kotlin`() { + testInline( + """ + |/src/main/kotlin/sample/sample.kt + |package sample; + | /** + | * a normal comment + | * + | * @return empty string when [Hash Map][java.util.HashMap.containsKey] doesn't contain value. + | * + | */ + |fun sample(): String { + | return "" + | } + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val functionPage = module.findTestType("sample", "sample") + functionPage.content.assertNode { + group { + header(1) { +"sample" } + } + divergentGroup { + divergentInstance { + divergent { + skipAllNotMatching() //Signature + } + after { + group { pWrapped("a normal comment") } + group { + header(4) { +"Return" } + comment { + +"empty string when " + link { +"Hash Map" } + +" doesn't contain value." + } + } + } + } + } + } + } + } + } + + + @Test + fun `list with links and description`() { + testInline( + """ + |/src/main/java/sample/DocGenProcessor.java + |package sample; + |/** + | * Static library support version of the framework's {@link java.lang.String}. + | * Used to write apps that run on platforms prior to Android 3.0. When running + | * on Android 3.0 or above, this implementation is still used; it does not try + | * to switch to the framework's implementation. See the framework {@link java.lang.String} + | * documentation for a class overview. + | * + | * <p>The main differences when using this support version instead of the framework version are: + | * <ul> + | * <li>Your activity must extend {@link java.lang.String FragmentActivity} + | * <li>You must call {@link java.util.HashMap#containsKey(java.lang.Object) FragmentActivity#getSupportFragmentManager} to get the + | * {@link java.util.HashMap FragmentManager} + | * </ul> + | * + | */ + |public class DocGenProcessor { } + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val classPage = module.findTestType("sample", "DocGenProcessor") + classPage.content.assertNode { + group { + header { +"DocGenProcessor" } + platformHinted { + group { + skipAllNotMatching() //Signature + } + group { + comment { + group { + +"Static library support version of the framework's " + link { +"java.lang.String" } + +". Used to write apps that run on platforms prior to Android 3.0." + +" When running on Android 3.0 or above, this implementation is still used; it does not try to switch to the framework's implementation. See the framework " + link { +"java.lang.String" } + +" documentation for a class overview. " //TODO this probably shouldnt have a space but it is minor + } + group { + +"The main differences when using this support version instead of the framework version are: " + } + list { + group { + +"Your activity must extend " + link { +"FragmentActivity" } + } + group { + +"You must call " + link { +"FragmentActivity#getSupportFragmentManager" } + +" to get the " + link { +"FragmentManager" } + } + } + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `documentation with table`() { + testInline( + """ + |/src/main/java/sample/DocGenProcessor.java + |package sample; + |/** + | * <table> + | * <caption>List of supported types</caption> + | * <tr> + | * <td>cell 11</td> <td>cell 21</td> + | * </tr> + | * <tr> + | * <td>cell 12</td> <td>cell 22</td> + | * </tr> + | * </table> + | */ + | public class DocGenProcessor { } + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val classPage = + module.findTestType("sample", "DocGenProcessor") + classPage.content.assertNode { + group { + header { +"DocGenProcessor" } + platformHinted { + group { + skipAllNotMatching() //Signature + } + comment { + table { + check { + caption!!.assertNode { + caption { + +"List of supported types" + } + } + } + group { + group { + +"cell 11" + } + group { + +"cell 21" + } + } + group { + group { + +"cell 12" + } + group { + +"cell 22" + } + } + } + } + } + } + skipAllNotMatching() + } + } + } + } + + + @Test + fun `undocumented parameter and other tags`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @author Kordyjan + | * @since 0.11 + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + group { pWrapped("comment to function") } + unnamedTag("Author") { comment { +"Kordyjan" } } + unnamedTag("Since") { comment { +"0.11" } } + } + } + } + } + } + } + } + + @Test + fun `single parameter`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @param abc comment to param + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + group { pWrapped("comment to function") } + header(4) { +"Parameters" } + table { + group { + +"abc" + check { + val textStyles = children.single { it is ContentText }.style + assertContains(textStyles, TextStyle.Underlined) + } + group { group { +"comment to param" } } + } + } + } + } + } + } + } + } + } + + @Test + fun `single parameter in class`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to class + | * @param abc comment to param + | */ + |class Foo(abc: String) + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Foo") + println(page.content) + page.content.assertNode { + group { + header(1) { +"Foo" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Foo", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + group { + pWrapped("comment to class") + } + header(4) { +"Parameters" } + table { + group { + +"abc" + check { + val textStyles = children.single { it is ContentText }.style + assertContains(textStyles, TextStyle.Underlined) + } + group { group { +"comment to param" } } + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `multiple parameters`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @param first comment to first param + | * @param second comment to second param + | * @param[third] comment to third param + | */ + |fun function(first: String, second: Int, third: Double) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), "", "", emptySet(), "function", null, + "first" to ParamAttributes(emptyMap(), emptySet(), "String"), + "second" to ParamAttributes(emptyMap(), emptySet(), "Int"), + "third" to ParamAttributes(emptyMap(), emptySet(), "Double") + ) + } + after { + group { group { group { +"comment to function" } } } + header(4) { +"Parameters" } + table { + group { + +"first" + check { + val textStyles = children.single { it is ContentText }.style + assertContains(textStyles, TextStyle.Underlined) + } + group { group { +"comment to first param" } } + } + group { + +"second" + check { + val textStyles = children.single { it is ContentText }.style + assertContains(textStyles, TextStyle.Underlined) + } + group { group { +"comment to second param" } } + } + group { + +"third" + check { + val textStyles = children.single { it is ContentText }.style + assertContains(textStyles, TextStyle.Underlined) + } + group { group { +"comment to third param" } } + } + } + } + } + } + } + } + } + } + + + @Test + fun `multiple parameters with not natural order`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @param c comment to c param + | * @param b comment to b param + | * @param[a] comment to a param + | */ + |fun function(c: String, b: Int, a: Double) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), "", "", emptySet(), "function", null, + "c" to ParamAttributes(emptyMap(), emptySet(), "String"), + "b" to ParamAttributes(emptyMap(), emptySet(), "Int"), + "a" to ParamAttributes(emptyMap(), emptySet(), "Double") + ) + } + after { + group { group { group { +"comment to function" } } } + header(4) { +"Parameters" } + table { + group { + +"c" + group { group { +"comment to c param" } } + } + group { + +"b" + group { group { +"comment to b param" } } + } + group { + +"a" + group { group { +"comment to a param" } } + } + } + + } + } + } + } + } + } + } + + @Test + fun `multiple parameters without function description`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @param first comment to first param + | * @param second comment to second param + | * @param[third] comment to third param + | */ + |fun function(first: String, second: Int, third: Double) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), "", "", emptySet(), "function", null, + "first" to ParamAttributes(emptyMap(), emptySet(), "String"), + "second" to ParamAttributes(emptyMap(), emptySet(), "Int"), + "third" to ParamAttributes(emptyMap(), emptySet(), "Double") + ) + } + after { + header(4) { +"Parameters" } + table { + group { + +"first" + group { group { +"comment to first param" } } + } + group { + +"second" + group { group { +"comment to second param" } } + } + group { + +"third" + group { group { +"comment to third param" } } + } + } + } + } + } + } + } + } + } + + @Test + fun `function with receiver`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @param abc comment to param + | * @receiver comment to receiver + | */ + |fun String.function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignatureWithReceiver( + emptyMap(), + "", + "", + emptySet(), + "String", + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + group { pWrapped("comment to function") } + group { + header(4) { +"Receiver" } + pWrapped("comment to receiver") + } + header(4) { +"Parameters" } + table { + group { + +"abc" + group { group { +"comment to param" } } + } + } + + } + } + } + } + } + } + } + + @Test + fun `missing parameter documentation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @param first comment to first param + | * @param[third] comment to third param + | */ + |fun function(first: String, second: Int, third: Double) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), "", "", emptySet(), "function", null, + "first" to ParamAttributes(emptyMap(), emptySet(), "String"), + "second" to ParamAttributes(emptyMap(), emptySet(), "Int"), + "third" to ParamAttributes(emptyMap(), emptySet(), "Double") + ) + } + after { + group { group { group { +"comment to function" } } } + header(4) { +"Parameters" } + table { + group { + +"first" + group { group { +"comment to first param" } } + } + group { + +"third" + group { group { +"comment to third param" } } + } + } + } + } + } + } + } + } + } + + @Test + fun `parameters mixed with other tags`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * comment to function + | * @param first comment to first param + | * @author Kordyjan + | * @param second comment to second param + | * @since 0.11 + | * @param[third] comment to third param + | */ + |fun function(first: String, second: Int, third: Double) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), "", "", emptySet(), "function", null, + "first" to ParamAttributes(emptyMap(), emptySet(), "String"), + "second" to ParamAttributes(emptyMap(), emptySet(), "Int"), + "third" to ParamAttributes(emptyMap(), emptySet(), "Double") + ) + } + after { + group { pWrapped("comment to function") } + unnamedTag("Author") { comment { +"Kordyjan" } } + unnamedTag("Since") { comment { +"0.11" } } + header(4) { +"Parameters" } + + table { + group { + +"first" + group { group { +"comment to first param" } } + } + group { + +"second" + group { group { +"comment to second param" } } + } + group { + +"third" + group { group { +"comment to third param" } } + } + } + } + } + } + } + } + } + } + + @Test + fun javaDocCommentWithDocumentedParameters() { + testInline( + """ + |/src/main/java/test/Main.java + |package test + | public class Main { + | + | /** + | * comment to function + | * @param first comment to first param + | * @param second comment to second param + | */ + | public void sample(String first, String second) { + | + | } + | } + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val sampleFunction = module.dfs { + it is MemberPageNode && it.dri.first() + .toString() == "test/Main/sample/#java.lang.String#java.lang.String/PointingToDeclaration/" + } as MemberPageNode + val forJvm = (sampleFunction.documentables.firstOrNull() as DFunction).parameters.mapNotNull { + val jvm = it.documentation.keys.first { it.analysisPlatform == Platform.jvm } + it.documentation[jvm] + } + + assertEquals(2, forJvm.size) + val (first, second) = forJvm.map { it.paramsDescription() } + assertEquals("comment to first param", first) + assertEquals("comment to second param", second) + } + } + } + + private fun DocumentationNode.paramsDescription(): String = + children.firstIsInstanceOrNull<Param>()?.root?.children?.first()?.children?.firstIsInstanceOrNull<Text>()?.body.orEmpty() + +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/properties/ContentForClassWithParamsAndPropertiesTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/properties/ContentForClassWithParamsAndPropertiesTest.kt new file mode 100644 index 00000000..d244567f --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/properties/ContentForClassWithParamsAndPropertiesTest.kt @@ -0,0 +1,272 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.properties + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.DClass +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.model.doc.* +import org.jetbrains.dokka.pages.ClasslikePageNode +import org.jetbrains.dokka.pages.RootPageNode +import kotlin.test.Test +import kotlin.test.assertEquals + +class ContentForClassWithParamsAndPropertiesTest : BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + @Test + fun `should work for a simple property`() { + propertyTest { rootPage -> + val node = rootPage.dfs { it.name == "LoadInitialParams" } as ClasslikePageNode + val actualDocsForPlaceholdersEnabled = + (node.documentables.firstOrNull() as DClass).constructors.first().parameters.find { it.name == "placeholdersEnabled" } + ?.documentation?.entries?.first()?.value + assertEquals(DocumentationNode(listOf(docsForPlaceholdersEnabled)), actualDocsForPlaceholdersEnabled) + } + } + + @Test + fun `should work for a simple with linebreak`() { + propertyTest { rootPage -> + val node = rootPage.dfs { it.name == "LoadInitialParams" } as ClasslikePageNode + val actualDocsForRequestedLoadSize = + (node.documentables.firstOrNull() as DClass).constructors.first().parameters.find { it.name == "requestedLoadSize" } + ?.documentation?.entries?.first()?.value + assertEquals(DocumentationNode(listOf(docsForRequestedLoadSize)), actualDocsForRequestedLoadSize) + } + } + + @Test + fun `should work with multiline property inline code`() { + propertyTest { rootPage -> + val node = rootPage.dfs { it.name == "LoadInitialParams" } as ClasslikePageNode + + val actualDocsForRequestedInitialKey = + (node.documentables.firstOrNull() as DClass).constructors.first().parameters.find { it.name == "requestedInitialKey" } + ?.documentation?.entries?.first()?.value + assertEquals(DocumentationNode(listOf(docsForRequestedInitialKey)), actualDocsForRequestedInitialKey) + } + } + + @Test + fun `constructor should only the param and constructor tags`() { + propertyTest { rootPage -> + val constructorDocs = Description( + root = CustomDocTag( + children = listOf( + P( + children = listOf( + Text("Creates an empty group.") + ) + ) + ), + emptyMap(), "MARKDOWN_FILE" + ) + ) + val node = rootPage.dfs { it.name == "LoadInitialParams" } as ClasslikePageNode + + val actualDocs = + (node.documentables.firstOrNull() as DClass).constructors.first().documentation.entries.first().value + assertEquals(DocumentationNode(listOf(constructorDocs, docsForParam)), actualDocs) + } + } + + @Test + fun `class should have all tags`() { + propertyTest { rootPage -> + val ownDescription = Description( + root = CustomDocTag( + children = listOf( + P( + children = listOf( + Text("Holder object for inputs to loadInitial.") + ) + ) + ), + emptyMap(), "MARKDOWN_FILE" + ) + ) + val node = rootPage.dfs { it.name == "LoadInitialParams" } as ClasslikePageNode + + val actualDocs = + (node.documentables.firstOrNull() as DClass).documentation.entries.first().value + assertEquals( + DocumentationNode( + listOf( + ownDescription, + docsForParam, + docsForRequestedInitialKey, + docsForRequestedLoadSize, + docsForPlaceholdersEnabled, + docsForConstructor + ) + ), + actualDocs + ) + } + } + + @Test + fun `property should also work with own docs that override the param tag`() { + propertyTest { rootPage -> + val ownDescription = Description( + root = CustomDocTag( + children = listOf( + P( + children = listOf( + Text("Own docs") + ) + ) + ), + emptyMap(), "MARKDOWN_FILE" + ) + ) + val node = rootPage.dfs { it.name == "ItemKeyedDataSource" } as ClasslikePageNode + + val actualDocs = + (node.documentables.firstOrNull() as DClass).properties.first().documentation.entries.first().value + assertEquals( + DocumentationNode(listOf(ownDescription)), + actualDocs + ) + } + } + + + private fun propertyTest(block: (RootPageNode) -> Unit) { + testInline( + """ |/src/main/kotlin/test/source.kt + |package test + |/** + | * @property tested Docs from class + | */ + |abstract class ItemKeyedDataSource<Key : Any, Value : Any> : DataSource<Key, Value>(ITEM_KEYED) { + | /** + | * Own docs + | */ + | val tested = "" + | + | /** + | * Holder object for inputs to loadInitial. + | * + | * @param Key Type of data used to query Value types out of the DataSource. + | * @property requestedInitialKey Load items around this key, or at the beginning of the data set + | * if `null` is passed. + | * + | * Note that this key is generally a hint, and may be ignored if you want to always load from + | * the beginning. + | * @property requestedLoadSize Requested number of items to load. + | * + | * Note that this may be larger than available data. + | * @property placeholdersEnabled Defines whether placeholders are enabled, and whether the + | * loaded total count will be ignored. + | * + | * @constructor Creates an empty group. + | */ + | open class LoadInitialParams<Key : Any>( + | @JvmField + | val requestedInitialKey: Key?, + | @JvmField + | val requestedLoadSize: Int, + | @JvmField + | val placeholdersEnabled: Boolean + | ) + |}""".trimIndent(), testConfiguration + ) { + pagesGenerationStage = block + } + } + + private val docsForPlaceholdersEnabled = Property( + root = CustomDocTag( + listOf( + P( + children = listOf( + Text("Defines whether placeholders are enabled, and whether the loaded total count will be ignored.") + ) + ) + ), emptyMap(), "MARKDOWN_FILE" + ), + name = "placeholdersEnabled" + ) + + private val docsForRequestedInitialKey = Property( + root = CustomDocTag( + listOf( + P( + children = listOf( + Text("Load items around this key, or at the beginning of the data set if "), + CodeInline( + listOf( + Text("null") + ) + ), + Text(" is passed.") + ), + params = emptyMap() + ), + P( + children = listOf( + Text("Note that this key is generally a hint, and may be ignored if you want to always load from the beginning.") + ) + ) + ), emptyMap(), "MARKDOWN_FILE" + ), + name = "requestedInitialKey" + ) + + private val docsForRequestedLoadSize = Property( + root = CustomDocTag( + listOf( + P( + children = listOf( + Text("Requested number of items to load.") + ) + ), + P( + children = listOf( + Text("Note that this may be larger than available data.") + ) + ) + ), emptyMap(), "MARKDOWN_FILE" + ), + name = "requestedLoadSize" + ) + + private val docsForConstructor = Constructor( + root = CustomDocTag( + children = listOf( + P( + children = listOf( + Text("Creates an empty group.") + ) + ) + ), + emptyMap(), "MARKDOWN_FILE" + ) + ) + + private val docsForParam = Param( + root = CustomDocTag( + children = listOf( + P( + children = listOf( + Text("Type of data used to query Value types out of the DataSource.") + ) + ) + ), + emptyMap(), "MARKDOWN_FILE" + ), + name = "Key" + ) +} + diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/receiver/ContentForReceiverTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/receiver/ContentForReceiverTest.kt new file mode 100644 index 00000000..d94c1106 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/receiver/ContentForReceiverTest.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.receiver + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.model.doc.Receiver +import org.jetbrains.dokka.model.doc.Text +import org.jetbrains.dokka.pages.ContentHeader +import org.jetbrains.dokka.pages.ContentText +import org.jetbrains.dokka.pages.MemberPageNode +import utils.docs +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class ContentForReceiverTest: BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + @Test + fun `should have docs for receiver`(){ + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + |/** + | * docs + | * @receiver docs for string + | */ + |fun String.asd2(): String = this + """.trimIndent(), + testConfiguration + ){ + documentablesTransformationStage = { module -> + with(module.packages.flatMap { it.functions }.first()){ + val receiver = docs().firstOrNull { it is Receiver } + assertNotNull(receiver) + val content = receiver.dfs { it is Text } as Text + assertEquals("docs for string", content.body) + } + } + pagesTransformationStage = { rootPageNode -> + val functionPage = rootPageNode.dfs { it is MemberPageNode } as MemberPageNode + val header = functionPage.content.dfs { it is ContentHeader && it.children.firstOrNull() is ContentText } + val text = functionPage.content.dfs { it is ContentText && it.text == "docs for string" } + + assertNotNull(header) + assertNotNull(text) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/samples/ContentForSamplesTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/samples/ContentForSamplesTest.kt new file mode 100644 index 00000000..d166d8f8 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/samples/ContentForSamplesTest.kt @@ -0,0 +1,207 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.samples + +import matchers.content.* +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.base.transformers.pages.KOTLIN_PLAYGROUND_SCRIPT +import org.jetbrains.dokka.model.DisplaySourceSet +import utils.TestOutputWriterPlugin +import utils.assertContains +import utils.classSignature +import utils.findTestType +import java.nio.file.Paths +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals + +class ContentForSamplesTest : BaseAbstractTest() { + private val testDataDir = getTestDataDir("content/samples").toAbsolutePath() + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + samples = listOf( + Paths.get("$testDataDir/samples.kt").toString(), + ) + } + } + } + + private val mppTestConfiguration = dokkaConfiguration { + moduleName = "example" + sourceSets { + val common = sourceSet { + name = "common" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt") + samples = listOf( + Paths.get("$testDataDir/samples.kt").toString(), + ) + } + sourceSet { + name = "jvm" + displayName = "jvm" + analysisPlatform = "jvm" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt") + samples = listOf( + Paths.get("$testDataDir/samples.kt").toString(), + ) + } + sourceSet { + name = "linuxX64" + displayName = "linuxX64" + analysisPlatform = "native" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/linuxX64Main/kotlin/pageMerger/Test.kt") + samples = listOf( + Paths.get("$testDataDir/samples.kt").toString(), + ) + } + } + } + + @Test + fun `samples block is rendered in the description`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + | /** + | * @sample [test.sampleForClassDescription] + | */ + |class Foo + """.trimIndent(), testConfiguration, + pluginOverrides = listOf(writerPlugin) + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Foo") + assertContains(page.embeddedResources, KOTLIN_PLAYGROUND_SCRIPT) + page.content.assertNode { + group { + header(1) { +"Foo" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Foo" + ) + header(4) { +"Samples" } + group { + codeBlock { + +"""| + |fun main() { + | //sampleStart + | print("Hello") + | //sampleEnd + |}""".trimMargin() + } + } + } + } + skipAllNotMatching() + } + } + renderingStage = { _, _ -> + assertNotEquals(-1, writerPlugin.writer.contents["root/test/-foo/index.html"]?.indexOf(KOTLIN_PLAYGROUND_SCRIPT)) + } + } + } + + @Test + fun `multiplatofrm class with samples in few platforms`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @sample [test.sampleForClassDescription] + |*/ + |expect open class Parent + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @sample unresolved + |*/ + |actual open class Parent + | + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual open class Parent + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + assertContains(page.embeddedResources, KOTLIN_PLAYGROUND_SCRIPT) + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"expect open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + header(4) { +"Samples" } + group { + codeBlock { + +"""| + |fun main() { + | //sampleStart + | print("Hello") + | //sampleEnd + |}""".trimMargin() + } + check { + sourceSets.assertSourceSet("common") + } + } + group { + +"unresolved" + check { + sourceSets.assertSourceSet("jvm") + } + } + } + } + skipAllNotMatching() + } + } + } + } +} + + +private fun Set<DisplaySourceSet>.assertSourceSet(expectedName: String) { + assertEquals(1, this.size) + assertEquals(expectedName, this.first().name) +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt new file mode 100644 index 00000000..fb72178b --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt @@ -0,0 +1,866 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.seealso + +import matchers.content.* +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.DisplaySourceSet +import org.jetbrains.dokka.pages.ContentDRILink +import utils.* +import kotlin.test.Test +import kotlin.test.assertEquals + +class ContentForSeeAlsoTest : BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOfNotNull(jvmStdlibPath) + analysisPlatform = "jvm" + } + } + } + + private val mppTestConfiguration = dokkaConfiguration { + moduleName = "example" + sourceSets { + val common = sourceSet { + name = "common" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "jvm" + displayName = "jvm" + analysisPlatform = "jvm" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt") + } + sourceSet { + name = "linuxX64" + displayName = "linuxX64" + analysisPlatform = "native" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/linuxX64Main/kotlin/pageMerger/Test.kt") + } + } + } + + @Test + fun `undocumented function`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @Test + fun `undocumented seealso`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see abc + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + header(4) { +"See also" } + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"abc" } + } + } + } + } + } + } + } + } + } + + @Test + fun `undocumented seealso without reference for class`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see abc + | */ + |class Foo() + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Foo") + println(page.content) + page.content.assertNode { + group { + header(1) { +"Foo" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Foo" + ) + header(4) { +"See also" } + table { + group { + +"abc" + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @OnlyDescriptors("No link for `abc` in K1") + @Test + fun `undocumented seealso with reference to parameter for class`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see abc + | */ + |class Foo(abc: String) + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Foo") + println(page.content) + page.content.assertNode { + group { + header(1) { +"Foo" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Foo", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + header(4) { +"See also" } + table { + group { + +"abc" // link { +"abc" } + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @OnlyDescriptors("issue #3179") + @Test + fun `undocumented seealso with reference to property for class`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see abc + | */ + |class Foo(val abc: String) + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Foo") + println(page.content) + page.content.assertNode { + group { + header(1) { +"Foo" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Foo", + "val abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + header(4) { +"See also" } + table { + group { + link { +"Foo.abc" } + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `documented seealso`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see abc Comment to abc + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + header(4) { +"See also" } + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"abc" } + group { + group { +"Comment to abc" } + } + } + } + } + } + } + } + } + } + } + + @OnlyDescriptors("issue #3179") + @Test + fun `documented seealso with reference to property for class`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see abc Comment to abc + | */ + |class Foo(val abc: String) + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "Foo") + println(page.content) + page.content.assertNode { + group { + header(1) { +"Foo" } + platformHinted { + classSignature( + emptyMap(), + "", + "", + emptySet(), + "Foo", + "val abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + header(4) { +"See also" } + table { + group { + link { +"Foo.abc" } + group { + group { +"Comment to abc" } + } + } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `should use fully qualified name for unresolved link`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see com.example.NonExistingClass description for non-existing + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + header(4) { +"See also" } + table { + group { + +"com.example.NonExistingClass" + group { + group { +"description for non-existing" } + } + } + } + } + } + } + } + } + } + } + + @Test + fun `undocumented seealso with stdlib link`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see Collection + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + header(4) { +"See also" } + table { + group { + link { + check { + assertEquals( + "kotlin.collections/Collection///PointingToDeclaration/", + (this as ContentDRILink).address.toString() + ) + } + +"Collection" + } + } + } + + } + } + } + } + } + } + } + + @Test + fun `documented seealso with stdlib link`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see Collection Comment to stdliblink + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + header(4) { +"See also" } + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"Collection" } + group { + group { +"Comment to stdliblink" } + } + } + + } + } + } + } + } + } + } + } + + @Test + fun `documented seealso with stdlib link with other tags`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * random comment + | * @see Collection Comment to stdliblink + | * @author pikinier20 + | * @since 0.11 + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + group { comment { +"random comment" } } + unnamedTag("Author") { comment { +"pikinier20" } } + unnamedTag("Since") { comment { +"0.11" } } + + header(4) { +"See also" } + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"Collection" } + group { + group { +"Comment to stdliblink" } + } + } + } + + } + } + } + } + } + } + } + + @Test + fun `documented multiple see also`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see abc Comment to abc1 + | * @see abc Comment to abc2 + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + header(4) { +"See also" } + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"abc" } + group { + group { +"Comment to abc2" } + } + } + } + + } + } + } + } + } + } + } + + @Test + fun `documented multiple see also mixed source`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @see abc Comment to abc1 + | * @see[Collection] Comment to collection + | */ + |fun function(abc: String) { + | println(abc) + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("test", "function") + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + after { + header(4) { +"See also" } + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"abc" } + group { + group { +"Comment to abc1" } + } + } + group { + //DRI should be "test//abc/#/-1/" + link { +"Collection" } + group { group { +"Comment to collection" } } + } + } + } + } + } + } + } + } + } + + @Test + fun `should prefix static function and property links with class name`() { + testInline( + """ + |/src/main/kotlin/com/example/package/CollectionExtensions.kt + |package com.example.util + | + |object CollectionExtensions { + | val property = "Hi" + | fun emptyList() {} + |} + | + |/src/main/kotlin/com/example/foo.kt + |package com.example + | + |import com.example.util.CollectionExtensions.property + |import com.example.util.CollectionExtensions.emptyList + | + |/** + | * @see [property] static property + | * @see [emptyList] static emptyList + | */ + |fun function() {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("com.example", "function") + + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "function", + returnType = null, + ) + } + after { + header(4) { +"See also" } + table { + group { + link { +"CollectionExtensions.property" } + group { + group { +"static property" } + } + } + group { + link { +"CollectionExtensions.emptyList" } + group { + group { +"static emptyList" } + } + } + } + } + } + } + } + } + } + } + + @Test + fun `multiplatform class with seealso in few platforms`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |/** + |* @see Unit + |*/ + |expect open class Parent + | + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |val x = 0 + |/** + |* @see x resolved + |* @see y unresolved + |*/ + |actual open class Parent + | + |/src/linuxX64Main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual open class Parent + | + """.trimMargin(), + mppTestConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.findTestType("pageMerger", "Parent") + page.content.assertNode { + group { + header(1) { +"Parent" } + platformHinted { + group { + +"expect open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + group { + +"actual open class " + link { + +"Parent" + } + } + header(4) { + +"See also" + check { + assertEquals(2, sourceSets.size) + } + } + table { + group { + link { +"Unit" } + check { + sourceSets.assertSourceSet("common") + } + } + group { + link { +"Unit" } + check { + sourceSets.assertSourceSet("jvm") + } + } + group { + link { +"x" } + group { group { +"resolved" } } + check { + sourceSets.assertSourceSet("jvm") + } + } + group { + +"y" + group { group { +"unresolved" } } + check { + sourceSets.assertSourceSet("jvm") + } + } + + check { + assertEquals(2, sourceSets.size) + } + } + } + } + skipAllNotMatching() + } + } + } + } +} + +private fun Set<DisplaySourceSet>.assertSourceSet(expectedName: String) { + assertEquals(1, this.size) + assertEquals(expectedName, this.first().name) +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt new file mode 100644 index 00000000..9a413e0e --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/signatures/ConstructorsSignaturesTest.kt @@ -0,0 +1,469 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.signatures + +import matchers.content.* +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.pages.BasicTabbedContentType +import org.jetbrains.dokka.pages.ContentPage +import kotlin.test.Test +import utils.OnlyDescriptors + +class ConstructorsSignaturesTest : BaseAbstractTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + @Test + fun `class name without parenthesis`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |class SomeClass + | + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "SomeClass" } as ContentPage + page.content.assertNode { + group { + header(1) { +"SomeClass" } + platformHinted { + group { + +"class " + link { +"SomeClass" } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `class name with empty parenthesis`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |class SomeClass() + | + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "SomeClass" } as ContentPage + page.content.assertNode { + group { + header(1) { +"SomeClass" } + platformHinted { + group { + +"class " + link { +"SomeClass" } + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `class with a parameter`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |class SomeClass(a: String) + | + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "SomeClass" } as ContentPage + page.content.assertNode { + group { + header(1) { +"SomeClass" } + platformHinted { + group { + +"class " + link { +"SomeClass" } + +"(" + group { + group { + +"a: " + group { link { +"String" } } + } + } + +")" + } + } + } + skipAllNotMatching() + } + } + } + } + + @Test + fun `class with a val parameter`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |class SomeClass(val a: String, var i: Int) + | + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "SomeClass" } as ContentPage + page.content.assertNode { + group { + header(1) { +"SomeClass" } + platformHinted { + group { + +"class " + link { +"SomeClass" } + +"(" + group { + group { + +"val a: " + group { link { +"String" } } + +", " + } + group { + +"var i: " + group { link { +"Int" } } + } + } + +")" + } + } + } + skipAllNotMatching() + } + } + } + } + + @OnlyDescriptors("Order of constructors is different in K2") + @Test + fun `class with a parameterless secondary constructor`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |class SomeClass(a: String) { + | constructor() + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "SomeClass" } as ContentPage + page.content.assertNode { + group { + header(1) { +"SomeClass" } + platformHinted { + group { + +"class " + link { +"SomeClass" } + +"(" + group { + group { + +"a: " + group { link { +"String" } } + } + } + +")" + } + } + } + tabbedGroup { + group { + tab(BasicTabbedContentType.CONSTRUCTOR) { + header { +"Constructors" } + table { + group { + link { +"SomeClass" } + platformHinted { + group { + +"constructor" + +"(" + +")" + } + group { + +"constructor" + +"(" + group { + group { + +"a: " + group { link { +"String" } } + } + } + +")" + } + } + } + } + } + } + skipAllNotMatching() + } + } + } + } + } + + + @OnlyDescriptors("Order of constructors is different in K2") + @Test + fun `class with a few documented constructors`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + | /** + | * some comment + | * @constructor ctor comment + | **/ + |class SomeClass(a: String){ + | /** + | * ctor one + | **/ + | constructor(): this("") + | + | /** + | * ctor two + | **/ + | constructor(b: Int): this("") + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "SomeClass" } as ContentPage + page.content.assertNode { + group { + header(1) { +"SomeClass" } + platformHinted { + group { + +"class " + link { +"SomeClass" } + +"(" + group { + group { + +"a: " + group { link { +"String" } } + } + } + +")" + } + skipAllNotMatching() + } + } + tabbedGroup { + group { + tab(BasicTabbedContentType.CONSTRUCTOR) { + header { +"Constructors" } + table { + group { + link { +"SomeClass" } + platformHinted { + group { + +"constructor" + +"(" + +")" + } + group { + group { + group { +"ctor one" } + } + } + group { + +"constructor" + +"(" + group { + group { + +"b: " + group { + link { +"Int" } + } + } + } + +")" + } + group { + group { + group { +"ctor two" } + } + } + group { + +"constructor" + +"(" + group { + group { + +"a: " + group { + link { +"String" } + } + } + } + +")" + } + group { + group { + group { +"ctor comment" } + } + } + } + } + } + } + } + skipAllNotMatching() + } + } + } + } + } + + @Test + fun `class with explicitly documented constructor`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + | /** + | * some comment + | * @constructor ctor comment + | **/ + |class SomeClass(a: String) + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "SomeClass" } as ContentPage + page.content.assertNode { + group { + header(1) { +"SomeClass" } + platformHinted { + group { + +"class " + link { +"SomeClass" } + +"(" + group { + group { + +"a: " + group { link { +"String" } } + } + } + +")" + } + skipAllNotMatching() + } + } + tabbedGroup { + group { + tab(BasicTabbedContentType.CONSTRUCTOR) { + header { +"Constructors" } + table { + group { + link { +"SomeClass" } + platformHinted { + group { + +"constructor" + +"(" + group { + group { + +"a: " + group { + link { +"String" } + } + } + } + +")" + } + group { + group { + group { +"ctor comment" } + } + } + } + } + } + } + } + skipAllNotMatching() + } + } + } + } + } + + @Test + fun `should render primary constructor, but not constructors block for annotation class`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |annotation class MyAnnotation(val param: String) {} + """.trimIndent(), + testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "MyAnnotation" } as ContentPage + page.content.assertNode { + group { + header(1) { +"MyAnnotation" } + platformHinted { + group { + +"annotation class " + link { +"MyAnnotation" } + +"(" + group { + group { + +"val param: " + group { link { +"String" } } + } + } + +")" + } + } + } + group { + group { + group { + header { +"Properties" } + table { + skipAllNotMatching() + } + } + } + } + } + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/signatures/ContentForSignaturesTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/signatures/ContentForSignaturesTest.kt new file mode 100644 index 00000000..8af9e082 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/signatures/ContentForSignaturesTest.kt @@ -0,0 +1,515 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.signatures + +import matchers.content.* +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.PackagePageNode +import utils.ParamAttributes +import utils.bareSignature +import utils.propertySignature +import utils.typealiasSignature +import kotlin.test.Test + +class ContentForSignaturesTest : BaseAbstractTest() { + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + documentedVisibilities = setOf( + DokkaConfiguration.Visibility.PUBLIC, + DokkaConfiguration.Visibility.PRIVATE, + DokkaConfiguration.Visibility.PROTECTED, + DokkaConfiguration.Visibility.INTERNAL, + ) + } + } + } + + @Test + fun `function`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |fun function(abc: String): String { + | return "Hello, " + abc + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + "String", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @Test + fun `private function`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |private fun function(abc: String): String { + | return "Hello, " + abc + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "private", + "", + emptySet(), + "function", + "String", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @Test + fun `open function`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |open fun function(abc: String): String { + | return "Hello, " + abc + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "open", + emptySet(), + "function", + "String", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @Test + fun `function without parameters`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |fun function(): String { + | return "Hello" + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "function", + returnType = "String", + ) + } + } + } + + } + } + } + } + + + @Test + fun `suspend function`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |suspend fun function(abc: String): String { + | return "Hello, " + abc + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "", + "", + setOf("suspend"), + "function", + "String", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @Test + fun `protected open suspend function`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |protected open suspend fun function(abc: String): String { + | return "Hello, " + abc + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "protected", + "open", + setOf("suspend"), + "function", + "String", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @Test + fun `protected open suspend inline function`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |protected open suspend inline fun function(abc: String): String { + | return "Hello, " + abc + |} + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + divergent { + bareSignature( + emptyMap(), + "protected", + "open", + setOf("inline", "suspend"), + "function", + "String", + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @Test + fun `property`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |val property: Int = 6 + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + propertySignature(emptyMap(), "", "", emptySet(), "val", "property", "Int", "6") + } + } + } + } + + @Test + fun `const property`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |const val property: Int = 6 + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + propertySignature(emptyMap(), "", "", setOf("const"), "val", "property", "Int", "6") + } + } + } + } + + @Test + fun `protected property`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |protected val property: Int = 6 + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + propertySignature(emptyMap(), "protected", "", emptySet(), "val", "property", "Int", "6") + } + } + } + } + + @Test + fun `protected lateinit property`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |protected lateinit var property: Int = 6 + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + propertySignature(emptyMap(), "protected", "", setOf("lateinit"), "var", "property", "Int", null) + } + } + } + } + + @Test + fun `should not display default value for mutable property`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |var property: Int = 6 + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + propertySignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = setOf(), + preposition = "var", + name = "property", + type = "Int", + value = null + ) + } + } + } + } + + @Test + fun `typealias to String`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |typealias Alias = String + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + typealiasSignature("Alias", "String") + } + } + } + } + + @Test + fun `typealias to Int`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |typealias Alias = Int + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + typealiasSignature("Alias", "Int") + } + } + } + } + + @Test + fun `typealias to type in same package`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |typealias Alias = X + |class X + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + typealiasSignature("Alias", "X") + } + } + } + } + + @Test + fun `typealias to type in different package`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + |import other.X + |typealias Alias = X + | + |/src/main/kotlin/test/source2.kt + |package other + |class X + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + typealiasSignature("Alias", "X") + } + } + } + } + + @Test + fun `typealias to type in different package with same name`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + |typealias Alias = other.Alias + | + |/src/main/kotlin/test/source2.kt + |package other + |class Alias + """.trimIndent(), testConfiguration + ) { + pagesTransformationStage = { module -> + val page = module.children.single { it.name == "test" } as PackagePageNode + page.content.assertNode { + typealiasSignature("Alias", "other.Alias") + } + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/content/typealiases/TypealiasTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/content/typealiases/TypealiasTest.kt new file mode 100644 index 00000000..4015e0f4 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/content/typealiases/TypealiasTest.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package content.typealiases + +import matchers.content.* +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.pages.ClasslikePageNode +import org.jetbrains.dokka.pages.PlatformHintedContent +import utils.assertNotNull +import kotlin.test.Test + + +class TypealiasTest : BaseAbstractTest() { + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOf(commonStdlibPath!!, jvmStdlibPath!!) + externalDocumentationLinks = listOf(stdlibExternalDocumentationLink) + } + } + } + + @Test + fun `typealias should have a dedicated page with full documentation`() { + testInline( + """ + |/src/main/kotlin/test/Test.kt + |package example + | + | /** + | * Brief text + | * + | * some text + | * + | * @see String + | * @throws Unit + | */ + | typealias A = String + """, + configuration + ) { + pagesTransformationStage = { module -> + val content = (module.dfs { it.name == "A" } as ClasslikePageNode).content + val platformHinted = content.dfs { it is PlatformHintedContent } + platformHinted.assertNotNull("platformHinted").assertNode { + group { + group { + group { + +"typealias " + group { group { link { +"A" } } } + +" = " + group { link { +"String" } } + } + } + + group { + group { + group { + group { +"Brief text" } + group { +"some text" } + } + } + } + + header { +"See also" } + table { + group { link { +"String" } } + } + + header { +"Throws" } + table { + group { group { link { +"Unit" } } } + } + } + } + } + } + } +} |