diff options
author | Paweł Marks <pmarks@virtuslab.com> | 2020-07-17 16:36:09 +0200 |
---|---|---|
committer | Paweł Marks <pmarks@virtuslab.com> | 2020-07-17 16:36:09 +0200 |
commit | 6996b1135f61c7d2cb60b0652c6a2691dda31990 (patch) | |
tree | d568096c25e31c28d14d518a63458b5a7526b896 /plugins/base/src/test | |
parent | de56cab76f556e5b4af0b8c8cb08d8b482b86d0a (diff) | |
parent | 1c3530dcbb50c347f80bef694829dbefe89eca77 (diff) | |
download | dokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.tar.gz dokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.tar.bz2 dokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.zip |
Merge branch 'dev-0.11.0'
Diffstat (limited to 'plugins/base/src/test')
64 files changed, 10852 insertions, 0 deletions
diff --git a/plugins/base/src/test/kotlin/basic/DRITest.kt b/plugins/base/src/test/kotlin/basic/DRITest.kt new file mode 100644 index 00000000..559a2dbf --- /dev/null +++ b/plugins/base/src/test/kotlin/basic/DRITest.kt @@ -0,0 +1,317 @@ +package basic + +import org.jetbrains.dokka.links.* +import org.jetbrains.dokka.links.Callable +import org.jetbrains.dokka.links.Nullable +import org.jetbrains.dokka.links.TypeConstructor +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.pages.ClasslikePageNode +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.MemberPageNode +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class DRITest : AbstractCoreTest() { + @Test + fun issue634() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package toplevel + | + |inline fun <T, R : Comparable<R>> Array<out T>.mySortBy( + | crossinline selector: (T) -> R?): Array<out T> = TODO() + |} + """.trimMargin(), + configuration + ) { + documentablesMergingStage = { module -> + val expected = TypeConstructor( + "kotlin.Function1", listOf( + TypeParam(listOf(Nullable(TypeConstructor("kotlin.Any", emptyList())))), + Nullable(TypeParam(listOf(TypeConstructor("kotlin.Comparable", listOf(SelfType))))) + ) + ) + val actual = module.packages.single() + .functions.single() + .dri.callable?.params?.single() + assertEquals(expected, actual) + } + } + } + + @Test + fun issue634WithImmediateNullableSelf() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package toplevel + | + |fun <T : Comparable<T>> Array<T>.doSomething(t: T?): Array<T> = TODO() + |} + """.trimMargin(), + configuration + ) { + documentablesMergingStage = { module -> + val expected = Nullable(TypeParam(listOf(TypeConstructor("kotlin.Comparable", listOf(SelfType))))) + val actual = module.packages.single() + .functions.single() + .dri.callable?.params?.single() + assertEquals(expected, actual) + } + } + } + + @Test + fun issue634WithGenericNullableReceiver() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package toplevel + | + |fun <T : Comparable<T>> T?.doSomethingWithNullable() = TODO() + |} + """.trimMargin(), + configuration + ) { + documentablesMergingStage = { module -> + val expected = Nullable(TypeParam(listOf(TypeConstructor("kotlin.Comparable", listOf(SelfType))))) + val actual = module.packages.single() + .functions.single() + .dri.callable?.receiver + assertEquals(expected, actual) + } + } + } + + @Test + fun issue642WithStarAndAny() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + analysisPlatform = "js" + sourceRoots = listOf("src/") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + | + |open class Bar<Z> + |class ReBarBar : Bar<StringBuilder>() + |class Foo<out T : Comparable<*>, R : List<Bar<*>>> + | + |fun <T : Comparable<Any?>> Foo<T, *>.qux(): String = TODO() + |fun <T : Comparable<*>> Foo<T, *>.qux(): String = TODO() + | + """.trimMargin(), + configuration + ) { + pagesGenerationStage = { module -> + // DRI(//qux/Foo[TypeParam(bounds=[kotlin.Comparable[kotlin.Any?]]),*]#/PointingToFunctionOrClasslike/) + val expectedDRI = DRI( + "", + null, + Callable( + "qux", TypeConstructor( + "Foo", listOf( + TypeParam( + listOf( + TypeConstructor( + "kotlin.Comparable", listOf( + Nullable(TypeConstructor("kotlin.Any", emptyList())) + ) + ) + ) + ), + StarProjection + ) + ), + emptyList() + ) + ) + + val driCount = module + .withDescendants() + .filterIsInstance<ContentPage>() + .sumBy { it.dri.count { dri -> dri == expectedDRI } } + + assertEquals(1, driCount) + } + } + } + + @Test + fun driForGenericClass(){ + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + testInline( + """ + |/src/main/kotlin/Test.kt + |package example + | + |class Sample<S>(first: S){ } + | + | + """.trimMargin(), + configuration + ) { + pagesGenerationStage = { module -> + val sampleClass = module.dfs { it.name == "Sample" } as ClasslikePageNode + val classDocumentable = sampleClass.documentable as DClass + + assertEquals( "example/Sample///PointingToDeclaration/", sampleClass.dri.first().toString()) + assertEquals("example/Sample///PointingToGenericParameters(0)/", classDocumentable.generics.first().dri.toString()) + } + } + } + + @Test + fun driForGenericFunction(){ + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOfNotNull(jvmStdlibPath) + } + } + } + testInline( + """ + |/src/main/kotlin/Test.kt + |package example + | + |class Sample<S>(first: S){ + | fun <T> genericFun(param1: String): Tuple<S,T> = TODO() + |} + | + | + """.trimMargin(), + configuration + ) { + pagesGenerationStage = { module -> + val sampleClass = module.dfs { it.name == "Sample" } as ClasslikePageNode + val functionNode = sampleClass.children.first { it.name == "genericFun" } as MemberPageNode + val functionDocumentable = functionNode.documentable as DFunction + val parameter = functionDocumentable.parameters.first() + + assertEquals("example/Sample/genericFun/#kotlin.String/PointingToDeclaration/", functionNode.dri.first().toString()) + + assertEquals(1, functionDocumentable.parameters.size) + assertEquals("example/Sample/genericFun/#kotlin.String/PointingToCallableParameters(0)/", parameter.dri.toString()) + //1 since from the function's perspective there is only 1 new generic declared + //The other one is 'inherited' from class + assertEquals( 1, functionDocumentable.generics.size) + assertEquals( "T", functionDocumentable.generics.first().name) + assertEquals( "example/Sample/genericFun/#kotlin.String/PointingToGenericParameters(0)/", functionDocumentable.generics.first().dri.toString()) + } + } + } + + @Test + fun driForFunctionNestedInsideInnerClass() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOfNotNull(jvmStdlibPath) + } + } + } + testInline( + """ + |/src/main/kotlin/Test.kt + |package example + | + |class Sample<S>(first: S){ + | inner class SampleInner { + | fun foo(): S = TODO() + | } + |} + | + | + """.trimMargin(), + configuration + ) { + pagesGenerationStage = { module -> + val sampleClass = module.dfs { it.name == "Sample" } as ClasslikePageNode + val sampleInner = sampleClass.children.first { it.name == "SampleInner" } as ClasslikePageNode + val foo = sampleInner.children.first { it.name == "foo" } as MemberPageNode + val documentable = foo.documentable as DFunction + + assertEquals(sampleClass.dri.first().toString(), (documentable.type as OtherParameter).declarationDRI.toString()) + assertEquals(0, documentable.generics.size) + } + } + } + + @Test + fun driForGenericExtensionFunction(){ + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + testInline( + """ + |/src/main/kotlin/Test.kt + |package example + | + | fun <T> List<T>.extensionFunction(): String = "" + | + """.trimMargin(), + configuration + ) { + pagesGenerationStage = { module -> + val extensionFunction = module.dfs { it.name == "extensionFunction" } as MemberPageNode + val documentable = extensionFunction.documentable as DFunction + + assertEquals( + "example//extensionFunction/kotlin.collections.List[TypeParam(bounds=[kotlin.Any?])]#/PointingToDeclaration/", + extensionFunction.dri.first().toString() + ) + assertEquals(1, documentable.generics.size) + assertEquals("T", documentable.generics.first().name) + assertEquals( + "example//extensionFunction/kotlin.collections.List[TypeParam(bounds=[kotlin.Any?])]#/PointingToGenericParameters(0)/", + documentable.generics.first().dri.toString() + ) + + } + } + } +} diff --git a/plugins/base/src/test/kotlin/basic/DokkaBasicTests.kt b/plugins/base/src/test/kotlin/basic/DokkaBasicTests.kt new file mode 100644 index 00000000..bceb79ae --- /dev/null +++ b/plugins/base/src/test/kotlin/basic/DokkaBasicTests.kt @@ -0,0 +1,42 @@ +package basic + +import org.jetbrains.dokka.pages.ClasslikePageNode +import org.jetbrains.dokka.pages.ModulePageNode +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest + +class DokkaBasicTests : AbstractCoreTest() { + + @Test + fun basic1() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package basic + | + |class Test { + | val tI = 1 + | fun tF() = 2 + |} + """.trimMargin(), + configuration + ) { + pagesGenerationStage = { + val root = it as ModulePageNode + assertTrue(root.getClasslikeToMemberMap().filterKeys { it.name == "Test" }.entries.firstOrNull()?.value?.size == 2) + } + } + } + + private fun ModulePageNode.getClasslikeToMemberMap() = + this.parentMap.filterValues { it is ClasslikePageNode }.entries.groupBy({ it.value }) { it.key } +} diff --git a/plugins/base/src/test/kotlin/basic/FailOnWarningTest.kt b/plugins/base/src/test/kotlin/basic/FailOnWarningTest.kt new file mode 100644 index 00000000..9d2c5825 --- /dev/null +++ b/plugins/base/src/test/kotlin/basic/FailOnWarningTest.kt @@ -0,0 +1,118 @@ +package basic + +import org.jetbrains.dokka.DokkaException +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.jetbrains.dokka.utilities.DokkaConsoleLogger +import org.jetbrains.dokka.utilities.DokkaLogger +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import testApi.logger.TestLogger + +class FailOnWarningTest : AbstractCoreTest() { + + @Test + fun `throws exception if one or more warnings were emitted`() { + val configuration = dokkaConfiguration { + failOnWarning = true + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin") + } + } + } + + assertThrows<DokkaException> { + testInline( + """ + |/src/main/kotlin + |package sample + """.trimIndent(), configuration + ) { + pluginsSetupStage = { + logger.warn("Warning!") + } + } + } + } + + @Test + fun `throws exception if one or more error were emitted`() { + val configuration = dokkaConfiguration { + failOnWarning = true + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin") + } + } + } + + assertThrows<DokkaException> { + testInline( + """ + |/src/main/kotlin + |package sample + """.trimIndent(), configuration + ) { + pluginsSetupStage = { + logger.error("Error!") + } + } + } + } + + @Test + fun `does not throw if now warning or error was emitted`() { + logger = TestLogger(ZeroErrorOrWarningCountDokkaLogger()) + + val configuration = dokkaConfiguration { + failOnWarning = true + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin") + } + } + } + + + testInline( + """ + |/src/main/kotlin + |package sample + """.trimIndent(), configuration + ) { + /* We expect no Exception */ + } + } + + @Test + fun `does not throw if disabled`() { + val configuration = dokkaConfiguration { + failOnWarning = false + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin") + } + } + } + + + testInline( + """ + |/src/main/kotlin + |package sample + """.trimIndent(), configuration + ) { + pluginsSetupStage = { + logger.warn("Error!") + logger.error("Error!") + } + } + } +} + +private class ZeroErrorOrWarningCountDokkaLogger( + logger: DokkaLogger = DokkaConsoleLogger +) : DokkaLogger by logger { + override var warningsCount: Int = 0 + override var errorsCount: Int = 0 +} diff --git a/plugins/base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt b/plugins/base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt new file mode 100644 index 00000000..bf78b847 --- /dev/null +++ b/plugins/base/src/test/kotlin/content/annotations/ContentForAnnotationsTest.kt @@ -0,0 +1,221 @@ +package content.annotations + +import matchers.content.* +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.PackagePageNode +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Test +import utils.ParamAttributes +import utils.bareSignature +import utils.propertySignature + + +class ContentForAnnotationsTest : AbstractCoreTest() { + + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + @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") + } + } + } + } + + @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") + } + } + } + } + + + @Test + fun `rich documented annotation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@MustBeDocumented + |@Retention(AnnotationRetention.SOURCE) + |@Target(AnnotationTarget.FIELD) + |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 + |) { + | enum class Status { + | UNCONFIRMED, CONFIRMED, FIXED, NOTABUG + | } + | class ABC + |} + |annotation class Reference(val value: Int) + | + | + |@BugReport( + | assignedTo = "me", + | testCase = BugReport.ABC::class, + | status = BugReport.Status.FIXED, + | ref = Reference(value = 2), + | reportedBy = [Reference(value = 2), Reference(value = 4)], + | showStopper = true + |) + |val ltint: Int = 5 + """.trimIndent(), testConfiguration + ) { + 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" + ) + ), "", "", emptySet(), "val", "ltint", "Int" + ) + } + } + } + } +} diff --git a/plugins/base/src/test/kotlin/content/annotations/DepredatedAndSinceKotlinTest.kt b/plugins/base/src/test/kotlin/content/annotations/DepredatedAndSinceKotlinTest.kt new file mode 100644 index 00000000..69de1bcd --- /dev/null +++ b/plugins/base/src/test/kotlin/content/annotations/DepredatedAndSinceKotlinTest.kt @@ -0,0 +1,103 @@ +package content.annotations + + +import matchers.content.* +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Test +import utils.ParamAttributes +import utils.bareSignature + + +class DepredatedAndSinceKotlinTest : AbstractCoreTest() { + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + } + } + } + + @Test + fun `function with deprecated annotation`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |@Deprecated("And some things that should not have been forgotten were lost. History became legend. Legend became myth.") + |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") + ) + } + } + } + } + } + } + } + + @Test + fun `function with since kotlin annotation`() { + 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/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt b/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt new file mode 100644 index 00000000..a9689bc5 --- /dev/null +++ b/plugins/base/src/test/kotlin/content/params/ContentForParamsTest.kt @@ -0,0 +1,611 @@ +package content.params + +import matchers.content.* +import org.jetbrains.dokka.Platform +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.ContentPage +import org.jetbrains.dokka.pages.MemberPageNode +import org.jetbrains.dokka.pages.dfs +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import org.junit.jupiter.api.Test +import utils.* + +class ContentForParamsTest : AbstractCoreTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + 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.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", 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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + pWrapped("comment to function") + } + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @Test + fun `undocumented parameter and other tags without function comment`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | /** + | * @author Kordyjan + | * @since 0.11 + | */ + |fun function(abc: String) { + | println(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 { + before { + unnamedTag("Author") { +"Kordyjan" } + unnamedTag("Since") { +"0.11" } + } + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + pWrapped("comment to function") + unnamedTag("Author") { +"Kordyjan" } + unnamedTag("Since") { +"0.11" } + } + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + pWrapped("comment to function") + header(2) { +"Parameters" } + group { + platformHinted { + table { + group { + +"abc" + group { +"comment to param" } + } + } + } + } + } + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + pWrapped("comment to function") + header(2) { +"Parameters" } + group { + platformHinted { + table { + group { + +"first" + group { +"comment to first param" } + } + group { + +"second" + group { +"comment to second param" } + } + group { + +"third" + group { +"comment to third param" } + } + } + } + } + } + divergent { + bareSignature( + emptyMap(), "", "", emptySet(), "function", null, + "first" to ParamAttributes(emptyMap(), emptySet(), "String"), + "second" to ParamAttributes(emptyMap(), emptySet(), "Int"), + "third" to ParamAttributes(emptyMap(), emptySet(), "Double") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + header(2) { +"Parameters" } + group { + platformHinted { + table { + group { + +"first" + group { +"comment to first param" } + } + group { + +"second" + group { +"comment to second param" } + } + group { + +"third" + group { +"comment to third param" } + } + } + } + } + } + divergent { + bareSignature( + emptyMap(), "", "", emptySet(), "function", null, + "first" to ParamAttributes(emptyMap(), emptySet(), "String"), + "second" to ParamAttributes(emptyMap(), emptySet(), "Int"), + "third" to ParamAttributes(emptyMap(), emptySet(), "Double") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + pWrapped("comment to function") + header(2) { +"Parameters" } + group { + platformHinted { + table { + group { + +"<receiver>" + group { +"comment to receiver" } + } + group { + +"abc" + group { +"comment to param" } + } + } + } + } + } + divergent { + bareSignatureWithReceiver( + emptyMap(), + "", + "", + emptySet(), + "String", + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + pWrapped("comment to function") + header(2) { +"Parameters" } + group { + platformHinted { + table { + group { + +"first" + group { +"comment to first param" } + } + group { + +"third" + group { +"comment to third param" } + } + } + } + } + } + divergent { + bareSignature( + emptyMap(), "", "", emptySet(), "function", null, + "first" to ParamAttributes(emptyMap(), emptySet(), "String"), + "second" to ParamAttributes(emptyMap(), emptySet(), "Int"), + "third" to ParamAttributes(emptyMap(), emptySet(), "Double") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + pWrapped("comment to function") + unnamedTag("Author") { +"Kordyjan" } + unnamedTag("Since") { +"0.11" } + header(2) { +"Parameters" } + + group { + platformHinted { + table { + group { + +"first" + group { +"comment to first param" } + } + group { + +"second" + group { +"comment to second param" } + } + group { + +"third" + group { +"comment to third param" } + } + } + } + } + } + divergent { + bareSignature( + emptyMap(), "", "", emptySet(), "function", null, + "first" to ParamAttributes(emptyMap(), emptySet(), "String"), + "second" to ParamAttributes(emptyMap(), emptySet(), "Int"), + "third" to ParamAttributes(emptyMap(), emptySet(), "Double") + ) + } + } + } + } + } + } + } + + @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.documentable as DFunction).parameters.mapNotNull { + val jvm = it.documentation.keys.first { it.analysisPlatform == Platform.jvm } + it.documentation[jvm] + } + + assert(forJvm.size == 2) + val (first, second) = forJvm.map { it.paramsDescription() } + assert(first == "comment to first param") + assert(second == "comment to second param") + } + } + } + + private fun DocumentationNode.paramsDescription(): String = + children.firstIsInstanceOrNull<Param>()?.root?.children?.firstIsInstanceOrNull<Text>()?.body.orEmpty() + +} diff --git a/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt b/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt new file mode 100644 index 00000000..24970660 --- /dev/null +++ b/plugins/base/src/test/kotlin/content/seealso/ContentForSeeAlsoTest.kt @@ -0,0 +1,459 @@ +package content.seealso + +import matchers.content.* +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Test +import utils.ParamAttributes +import utils.bareSignature +import utils.pWrapped +import utils.unnamedTag + +class ContentForSeeAlsoTest : AbstractCoreTest() { + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + 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.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", + 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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + header(2) { +"See also" } + group { + platformHinted { + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"abc" } + group { } + } + } + } + } + } + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + header(2) { +"See also" } + group { + platformHinted { + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"abc" } + group { +"Comment to abc" } + } + } + } + } + } + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + header(2) { +"See also" } + group { + platformHinted { + table { + group { + //DRI should be "kotlin.collections/Collection////" + link { +"Collection" } + group { } + } + } + } + } + } + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + header(2) { +"See also" } + group { + platformHinted { + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"Collection" } + group { +"Comment to stdliblink" } + } + } + } + } + } + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + pWrapped("random comment") + unnamedTag("Author") { +"pikinier20" } + unnamedTag("Since") { +"0.11" } + + header(2) { +"See also" } + group { + platformHinted { + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"Collection" } + group { +"Comment to stdliblink" } + } + } + } + } + } + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + header(2) { +"See also" } + group { + platformHinted { + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"abc" } + group { +"Comment to abc2" } + } + } + } + } + } + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } + + @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.children.single { it.name == "test" } + .children.single { it.name == "function" } as ContentPage + page.content.assertNode { + group { + header(1) { +"function" } + } + divergentGroup { + divergentInstance { + before { + header(2) { +"See also" } + group { + platformHinted { + table { + group { + //DRI should be "test//abc/#/-1/" + link { +"abc" } + group { +"Comment to abc1" } + } + group { + //DRI should be "test//abc/#/-1/" + link { +"Collection" } + group { +"Comment to collection" } + } + } + } + } + } + divergent { + bareSignature( + emptyMap(), + "", + "", + emptySet(), + "function", + null, + "abc" to ParamAttributes(emptyMap(), emptySet(), "String") + ) + } + } + } + } + } + } + } +} diff --git a/plugins/base/src/test/kotlin/content/signatures/ContentForSignaturesTest.kt b/plugins/base/src/test/kotlin/content/signatures/ContentForSignaturesTest.kt new file mode 100644 index 00000000..cabe822d --- /dev/null +++ b/plugins/base/src/test/kotlin/content/signatures/ContentForSignaturesTest.kt @@ -0,0 +1,456 @@ +package content.signatures + +import matchers.content.* +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.doc.Text +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Test +import utils.ParamAttributes +import utils.bareSignature +import utils.propertySignature +import utils.typealiasSignature + +class ContentForSignaturesTest : AbstractCoreTest() { + + private val testConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + includeNonPublic = true + } + } + } + + @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") + } + } + } + } + + @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") + } + } + } + } + + @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") + } + } + } + } + + @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") + } + } + } + } + + @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", "other.X") + } + } + } + } +} diff --git a/plugins/base/src/test/kotlin/content/signatures/SkippingParenthesisForConstructorsTest.kt b/plugins/base/src/test/kotlin/content/signatures/SkippingParenthesisForConstructorsTest.kt new file mode 100644 index 00000000..7de48664 --- /dev/null +++ b/plugins/base/src/test/kotlin/content/signatures/SkippingParenthesisForConstructorsTest.kt @@ -0,0 +1,254 @@ +package content.signatures + +import matchers.content.* +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Test +import utils.functionSignature + +class ConstructorsSignaturesTest : AbstractCoreTest() { + 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" } + +"(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) + | + """.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" } + +"(a:" // TODO: Make sure if we still do not want to have "val" here + group { link { +"String" } } + +")" + } + } + } + skipAllNotMatching() + } + } + } + } + + @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" } + +"(a:" + group { link { +"String" } } + +")" + } + } + } + group { + header { +"Constructors" } + table { + group { + link { +"<init>" } + functionSignature( + annotations = emptyMap(), + visibility = "", + modifier = "", + keywords = emptySet(), + name = "<init>" + ) + } + } + 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 { + skipAllNotMatching() + group { + +"class" + link { +"SomeClass" } + +"(a:" + group { link { +"String" } } + +")" + } + } + } + group { + header { +"Constructors" } + table { + group { + link { +"<init>" } + platformHinted { + group { + group { + +"ctor comment" + } + } + group { + +"fun" + link { +"<init>" } + +"(a:" + group { + link { +"String" } + } + +")" + } + } + } + } + skipAllNotMatching() + } + } + } + } + } +} diff --git a/plugins/base/src/test/kotlin/enums/EnumsTest.kt b/plugins/base/src/test/kotlin/enums/EnumsTest.kt new file mode 100644 index 00000000..6a973f8e --- /dev/null +++ b/plugins/base/src/test/kotlin/enums/EnumsTest.kt @@ -0,0 +1,234 @@ +package enums + +import matchers.content.* +import org.jetbrains.dokka.model.ConstructorValues +import org.jetbrains.dokka.model.DEnum +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class EnumsTest : AbstractCoreTest() { + + @Test + fun basicEnum() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package enums + | + |enum class Test { + | E1, + | E2 + |} + """.trimMargin(), + configuration + ) { + pagesGenerationStage = { + val map = it.getClasslikeToMemberMap() + val test = map.filterKeys { it.name == "Test" }.values.firstOrNull() + assertTrue(test != null) { "Test not found" } + assertTrue(test!!.any { it.name == "E1" } && test.any { it.name == "E2" }) { "Enum entries missing in parent" } + } + } + } + + @Test + fun enumWithCompanion() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package enums + | + |enum class Test { + | E1, + | E2; + | companion object {} + |} + """.trimMargin(), + configuration + ) { + documentablesTransformationStage = { m -> + m.packages.let { p -> + assertTrue(p.isNotEmpty(), "Package list cannot be empty") + p.first().classlikes.let { c -> + assertTrue(c.isNotEmpty(), "Classlikes list cannot be empty") + + val enum = c.first() as DEnum + assertEquals(enum.name, "Test") + assertEquals(enum.entries.count(), 2) + assertNotNull(enum.companion) + } + } + } + pagesGenerationStage = { module -> + val map = module.getClasslikeToMemberMap() + val test = map.filterKeys { it.name == "Test" }.values.firstOrNull() + assertNotNull(test, "Test not found") + assertTrue(test!!.any { it.name == "E1" } && test.any { it.name == "E2" }) { "Enum entries missing in parent" } + } + } + } + + @Test + fun enumWithConstructor() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package enums + | + | + |enum class Test(name: String, index: Int, excluded: Boolean) { + | E1("e1", 1, true), + | E2("e2", 2, false); + |} + """.trimMargin(), + configuration + ) { + documentablesTransformationStage = { m -> + m.packages.let { p -> + p.first().classlikes.let { c -> + val enum = c.first() as DEnum + val (first, second) = enum.entries + + assertEquals(1, first.extra.allOfType<ConstructorValues>().size) + assertEquals(1, second.extra.allOfType<ConstructorValues>().size) + assertEquals(listOf("\"e1\"", "1", "true"), first.extra.allOfType<ConstructorValues>().first().values.values.first()) + assertEquals(listOf("\"e2\"", "2", "false"), second.extra.allOfType<ConstructorValues>().first().values.values.first()) + } + } + } + pagesGenerationStage = { module -> + val entryPage = module.dfs { it.name == "E1" } as ClasslikePageNode + val signaturePart = (entryPage.content.dfs { + it is ContentGroup && it.dci.toString() == "[enums/Test.E1///PointingToDeclaration/][Symbol]" + } as ContentGroup) + assertEquals("(\"e1\", 1, true)", signaturePart.constructorSignature()) + } + } + } + + @Test + fun enumWithMethods() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package enums + | + | + |interface Sample { + | fun toBeImplemented(): String + |} + | + |enum class Test: Sample { + | E1 { + | override fun toBeImplemented(): String = "e1" + | } + |} + """.trimMargin(), + configuration + ) { + documentablesTransformationStage = { m -> + m.packages.let { p -> + p.first().classlikes.let { c -> + val enum = c.first { it is DEnum } as DEnum + val first = enum.entries.first() + + assertEquals(1, first.extra.allOfType<ConstructorValues>().size) + assertEquals(emptyList<String>(), first.extra.allOfType<ConstructorValues>().first().values.values.first()) + assertNotNull(first.functions.find { it.name == "toBeImplemented" }) + } + } + } + } + } + + @Test + fun enumWithAnnotationsOnEntries(){ + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package enums + | + |enum class Test { + | /** + | Sample docs for E1 + | **/ + | @SinceKotlin("1.3") // This annotation is transparent due to lack of @MustBeDocumented annotation + | E1 + |} + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { m -> + val entryNode = m.children.first { it.name == "enums" }.children.first { it.name == "Test" }.children.first() as ClasslikePageNode + val signature = (entryNode.content as ContentGroup).dfs { it is ContentGroup && it.dci.toString() == "[enums/Test.E1///PointingToDeclaration/][Cover]" } as ContentGroup + + signature.assertNode { + header(1) { +"E1" } + platformHinted { + group { + group { + + "Sample docs for E1" + } + } + group { + group { + link { +"E1" } + +"()" + } + } + } + } + } + } + } + + + fun RootPageNode.getClasslikeToMemberMap() = + this.parentMap.filterValues { it is ClasslikePageNode }.entries.groupBy({ it.value }) { it.key } + + private fun ContentGroup.constructorSignature(): String = + (children.single() as ContentGroup).children.drop(1).joinToString(separator = "") { (it as ContentText).text } +} diff --git a/plugins/base/src/test/kotlin/expect/AbstractExpectTest.kt b/plugins/base/src/test/kotlin/expect/AbstractExpectTest.kt new file mode 100644 index 00000000..4dfdc410 --- /dev/null +++ b/plugins/base/src/test/kotlin/expect/AbstractExpectTest.kt @@ -0,0 +1,104 @@ +package expect + +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions.assertTrue +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.util.concurrent.TimeUnit + +abstract class AbstractExpectTest( + val testDir: Path? = Paths.get("src/test", "resources", "expect"), + val formats: List<String> = listOf("html") +) : AbstractCoreTest() { + + protected fun generateOutput(path: Path, outFormat: String): Path? { + val config = dokkaConfiguration { + format = outFormat + sourceSets { + sourceSet { + sourceRoots = listOf(path.toAbsolutePath().asString()) + } + } + } + + var result: Path? = null + testFromData(config, cleanupOutput = false) { + renderingStage = { _, context -> result = Paths.get(context.configuration.outputDir) } + } + return result + } + + protected fun compareOutput(expected: Path, obtained: Path?, gitTimeout: Long = 500) { + obtained?.let { path -> + val gitCompare = ProcessBuilder( + "git", + "--no-pager", + "diff", + expected.asString(), + path.asString() + ).also { logger.info("git diff command: ${it.command().joinToString(" ")}") } + .also { it.redirectErrorStream() }.start() + + assertTrue(gitCompare.waitFor(gitTimeout, TimeUnit.MILLISECONDS)) { "Git timed out after $gitTimeout" } + gitCompare.inputStream.bufferedReader().lines().forEach { logger.info(it) } + assertTrue(gitCompare.exitValue() == 0) { "${path.fileName}: outputs don't match" } + } ?: throw AssertionError("obtained path is null") + } + + protected fun compareOutputWithExcludes( + expected: Path, + obtained: Path?, + excludes: List<String>, + timeout: Long = 500 + ) { + obtained?.let { path -> + val (res, out, err) = runDiff(expected, obtained, excludes, timeout) + assertTrue(res == 0, "Outputs differ:\nstdout - $out\n\nstderr - ${err ?: ""}") + } ?: throw AssertionError("obtained path is null") + } + + protected fun runDiff(exp: Path, obt: Path, excludes: List<String>, timeout: Long): ProcessResult = + ProcessBuilder().command( + listOf("diff", "-ru") + excludes.flatMap { listOf("-x", it) } + listOf("--", exp.asString(), obt.asString()) + ).also { + it.redirectErrorStream() + }.start().also { assertTrue(it.waitFor(timeout, TimeUnit.MILLISECONDS), "diff timed out") }.let { + ProcessResult(it.exitValue(), it.inputStream.bufferResult()) + } + + + protected fun testOutput(p: Path, outFormat: String) { + val expectOut = p.resolve("out/$outFormat") + val testOut = generateOutput(p.resolve("src"), outFormat) + .also { logger.info("Test out: ${it?.asString()}") } + + compareOutput(expectOut.toAbsolutePath(), testOut?.toAbsolutePath()) + testOut?.deleteRecursively() + } + + protected fun testOutputWithExcludes( + p: Path, + outFormat: String, + ignores: List<String> = emptyList(), + timeout: Long = 500 + ) { + val expected = p.resolve("out/$outFormat") + generateOutput(p.resolve("src"), outFormat) + ?.let { obtained -> + compareOutputWithExcludes(expected, obtained, ignores, timeout) + + obtained.deleteRecursively() + } ?: throw AssertionError("Output not generated for ${p.fileName}") + } + + protected fun generateExpect(p: Path, outFormat: String) { + val out = p.resolve("out/$outFormat/") + Files.createDirectories(out) + + val ret = generateOutput(p.resolve("src"), outFormat) + Files.list(out).forEach { it.deleteRecursively() } + ret?.let { Files.list(it).forEach { f -> f.copyRecursively(out.resolve(f.fileName)) } } + } + +} diff --git a/plugins/base/src/test/kotlin/expect/ExpectGenerator.kt b/plugins/base/src/test/kotlin/expect/ExpectGenerator.kt new file mode 100644 index 00000000..cb3313f9 --- /dev/null +++ b/plugins/base/src/test/kotlin/expect/ExpectGenerator.kt @@ -0,0 +1,13 @@ +package expect + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test + +class ExpectGenerator : AbstractExpectTest() { + + @Disabled + @Test + fun generateAll() = testDir?.dirsWithFormats(formats).orEmpty().forEach { (p, f) -> + generateExpect(p, f) + } +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/expect/ExpectTest.kt b/plugins/base/src/test/kotlin/expect/ExpectTest.kt new file mode 100644 index 00000000..bed841a6 --- /dev/null +++ b/plugins/base/src/test/kotlin/expect/ExpectTest.kt @@ -0,0 +1,24 @@ +package expect + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.DynamicTest.dynamicTest +import org.junit.jupiter.api.TestFactory + +class ExpectTest : AbstractExpectTest() { + private val ignores: List<String> = listOf( + "images", + "scripts", + "images", + "styles", + "*.js", + "*.css", + "*.svg", + "*.map" + ) + + @Disabled + @TestFactory + fun expectTest() = testDir?.dirsWithFormats(formats).orEmpty().map { (p, f) -> + dynamicTest("${p.fileName}-$f") { testOutputWithExcludes(p, f, ignores) } + } +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/expect/ExpectUtils.kt b/plugins/base/src/test/kotlin/expect/ExpectUtils.kt new file mode 100644 index 00000000..1f54c20e --- /dev/null +++ b/plugins/base/src/test/kotlin/expect/ExpectUtils.kt @@ -0,0 +1,28 @@ +package expect + +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.Path +import kotlin.streams.toList + +data class ProcessResult(val code: Int, val out: String, val err: String? = null) + +internal fun Path.dirsWithFormats(formats: List<String>): List<Pair<Path, String>> = + Files.list(this).toList().filter { Files.isDirectory(it) }.flatMap { p -> formats.map { p to it } } + +internal fun Path.asString() = normalize().toString() +internal fun Path.deleteRecursively() = toFile().deleteRecursively() + +internal fun Path.copyRecursively(target: Path) = toFile().copyRecursively(target.toFile()) + +internal fun Path.listRecursively(filter: (Path) -> Boolean): List<Path> = when { + Files.isDirectory(this) -> listOfNotNull(takeIf(filter)) + Files.list(this).toList().flatMap { + it.listRecursively( + filter + ) + } + Files.isRegularFile(this) -> listOfNotNull(this.takeIf(filter)) + else -> emptyList() + } + +internal fun InputStream.bufferResult(): String = this.bufferedReader().lines().toList().joinToString("\n")
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/filter/DeprecationFilterTest.kt b/plugins/base/src/test/kotlin/filter/DeprecationFilterTest.kt new file mode 100644 index 00000000..c8b9f2d4 --- /dev/null +++ b/plugins/base/src/test/kotlin/filter/DeprecationFilterTest.kt @@ -0,0 +1,173 @@ +package filter + +import org.jetbrains.dokka.PackageOptionsImpl +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class DeprecationFilterTest : AbstractCoreTest() { + @Test + fun `function with false global skipDeprecated`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + skipDeprecated = false + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package example + | + |fun testFunction() { } + | + | + | + """.trimMargin(), + configuration + ) { + documentablesFirstTransformationStep = { + Assertions.assertTrue( + it.component2().packages.first().functions.size == 1 + ) + } + } + } + @Test + fun `deprecated function with false global skipDeprecated`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + skipDeprecated = false + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package example + | + |@Deprecated("dep") + |fun testFunction() { } + | + | + """.trimMargin(), + configuration + ) { + documentablesFirstTransformationStep = { + Assertions.assertTrue( + it.component2().packages.first().functions.size == 1 + ) + } + } + } + @Test + fun `deprecated function with true global skipDeprecated`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + skipDeprecated = true + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package example + | + |@Deprecated("dep") + |fun testFunction() { } + | + | + """.trimMargin(), + configuration + ) { + documentablesFirstTransformationStep = { + Assertions.assertTrue( + it.component2().packages.first().functions.size == 0 + ) + } + } + } + @Test + fun `deprecated function with false global true package skipDeprecated`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + skipDeprecated = false + perPackageOptions = mutableListOf( + PackageOptionsImpl("example", + true, + false, + true, + false) + ) + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package example + | + |@Deprecated("dep") + |fun testFunction() { } + | + | + """.trimMargin(), + configuration + ) { + documentablesFirstTransformationStep = { + Assertions.assertTrue( + it.component2().packages.first().functions.size == 0 + ) + } + } + } + @Test + fun `deprecated function with true global false package skipDeprecated`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + skipDeprecated = true + perPackageOptions = mutableListOf( + PackageOptionsImpl("example", + false, + false, + false, + false) + ) + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package example + | + |@Deprecated("dep") + |fun testFunction() { } + | + | + """.trimMargin(), + configuration + ) { + documentablesFirstTransformationStep = { + Assertions.assertTrue( + it.component2().packages.first().functions.size == 1 + ) + } + } + } +} diff --git a/plugins/base/src/test/kotlin/filter/EmptyPackagesFilterTest.kt b/plugins/base/src/test/kotlin/filter/EmptyPackagesFilterTest.kt new file mode 100644 index 00000000..e5b9e9c2 --- /dev/null +++ b/plugins/base/src/test/kotlin/filter/EmptyPackagesFilterTest.kt @@ -0,0 +1,63 @@ +package filter + +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class EmptyPackagesFilterTest : AbstractCoreTest() { + @Test + fun `empty package with false skipEmptyPackages`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + skipEmptyPackages = false + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package example + | + | + | + """.trimMargin(), + configuration + ) { + documentablesFirstTransformationStep = { + Assertions.assertTrue( + it.component2().packages.isNotEmpty() + ) + } + } + } + @Test + fun `empty package with true skipEmptyPackages`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + skipEmptyPackages = true + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package example + | + | + """.trimMargin(), + configuration + ) { + documentablesFirstTransformationStep = { + Assertions.assertTrue( + it.component2().packages.isEmpty() + ) + } + } + } +} diff --git a/plugins/base/src/test/kotlin/filter/VisibilityFilterTest.kt b/plugins/base/src/test/kotlin/filter/VisibilityFilterTest.kt new file mode 100644 index 00000000..192de449 --- /dev/null +++ b/plugins/base/src/test/kotlin/filter/VisibilityFilterTest.kt @@ -0,0 +1,173 @@ +package filter + +import org.jetbrains.dokka.PackageOptionsImpl +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class VisibilityFilterTest : AbstractCoreTest() { + @Test + fun `public function with false global includeNonPublic`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + includeNonPublic = false + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package example + | + |fun testFunction() { } + | + | + | + """.trimMargin(), + configuration + ) { + documentablesFirstTransformationStep = { + Assertions.assertTrue( + it.component2().packages.first().functions.size == 1 + ) + } + } + } + @Test + fun `private function with false global includeNonPublic`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + includeNonPublic = false + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package example + | + |private fun testFunction() { } + | + | + | + """.trimMargin(), + configuration + ) { + documentablesFirstTransformationStep = { + Assertions.assertTrue( + it.component2().packages.first().functions.size == 0 + ) + } + } + } + @Test + fun `private function with true global includeNonPublic`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + includeNonPublic = true + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package example + | + |private fun testFunction() { } + | + | + | + """.trimMargin(), + configuration + ) { + documentablesFirstTransformationStep = { + Assertions.assertTrue( + it.component2().packages.first().functions.size == 1 + ) + } + } + } + @Test + fun `private function with false global true package includeNonPublic`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + includeNonPublic = false + perPackageOptions = mutableListOf( + PackageOptionsImpl("example", + true, + false, + false, + false) + ) + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package example + | + |private fun testFunction() { } + | + | + | + """.trimMargin(), + configuration + ) { + documentablesFirstTransformationStep = { + Assertions.assertTrue( + it.component2().packages.first().functions.size == 1 + ) + } + } + } + @Test + fun `private function with true global false package includeNonPublic`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/basic/Test.kt") + includeNonPublic = true + perPackageOptions = mutableListOf( + PackageOptionsImpl("example", + false, + false, + false, + false) + ) + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + |package example + | + |private fun testFunction() { } + | + | + | + """.trimMargin(), + configuration + ) { + documentablesFirstTransformationStep = { + Assertions.assertTrue( + it.component2().packages.first().functions.size == 0 + ) + } + } + } +} diff --git a/plugins/base/src/test/kotlin/issues/IssuesTest.kt b/plugins/base/src/test/kotlin/issues/IssuesTest.kt new file mode 100644 index 00000000..7b065349 --- /dev/null +++ b/plugins/base/src/test/kotlin/issues/IssuesTest.kt @@ -0,0 +1,73 @@ +package issues + +import org.jetbrains.dokka.model.DClass +import org.jetbrains.dokka.model.DFunction +import org.junit.jupiter.api.Test +import utils.AbstractModelTest +import utils.name + +class IssuesTest : AbstractModelTest("/src/main/kotlin/issues/Test.kt", "issues") { + + @Test + fun errorClasses() { + inlineModelTest( + """ + |class Test(var value: String) { + | fun test(): List<String> = emptyList() + | fun brokenApply(v: String) = apply { value = v } + | + | fun brokenRun(v: String) = run { + | value = v + | this + | } + | + | fun brokenLet(v: String) = let { + | it.value = v + | it + | } + | + | fun brokenGenerics() = listOf("a", "b", "c") + | + | fun working(v: String) = doSomething() + | + | fun doSomething(): String = "Hello" + |} + """, + configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOfNotNull(jvmStdlibPath) + } + } + } + ) { + with((this / "issues" / "Test").cast<DClass>()) { + (this / "working").cast<DFunction>().type.name equals "String" + (this / "doSomething").cast<DFunction>().type.name equals "String" + (this / "brokenGenerics").cast<DFunction>().type.name equals "List" + (this / "brokenApply").cast<DFunction>().type.name equals "Test" + (this / "brokenRun").cast<DFunction>().type.name equals "Test" + (this / "brokenLet").cast<DFunction>().type.name equals "Test" + } + } + } + + //@Test + // fun errorClasses() { + // checkSourceExistsAndVerifyModel("testdata/issues/errorClasses.kt", + // modelConfig = ModelConfig(analysisPlatform = analysisPlatform, withJdk = true, withKotlinRuntime = true)) { model -> + // val cls = model.members.single().members.single() + // + // fun DocumentationNode.returnType() = this.details.find { it.kind == NodeKind.Type }?.name + // assertEquals("Test", cls.members[1].returnType()) + // assertEquals("Test", cls.members[2].returnType()) + // assertEquals("Test", cls.members[3].returnType()) + // assertEquals("List", cls.members[4].returnType()) + // assertEquals("String", cls.members[5].returnType()) + // assertEquals("String", cls.members[6].returnType()) + // assertEquals("String", cls.members[7].returnType()) + // } + // } + +} diff --git a/plugins/base/src/test/kotlin/linkableContent/LinkableContentTest.kt b/plugins/base/src/test/kotlin/linkableContent/LinkableContentTest.kt new file mode 100644 index 00000000..a8fc9f6d --- /dev/null +++ b/plugins/base/src/test/kotlin/linkableContent/LinkableContentTest.kt @@ -0,0 +1,225 @@ +package linkableContent + +import org.jetbrains.dokka.SourceLinkDefinitionImpl +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.transformers.pages.samples.DefaultSamplesTransformer +import org.jetbrains.dokka.base.transformers.pages.sourcelinks.SourceLinksTransformer +import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder +import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.jetbrains.kotlin.utils.addToStdlib.cast +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.nio.file.Paths + +class LinkableContentTest : AbstractCoreTest() { + + @Test + fun `Include module and package documentation`() { + + val testDataDir = getTestDataDir("multiplatform/basicMultiplatformTest").toAbsolutePath() + val includesDir = getTestDataDir("linkable/includes").toAbsolutePath() + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + moduleName = "example" + analysisPlatform = "js" + sourceRoots = listOf("jsMain", "commonMain", "jvmAndJsSecondCommonMain").map { + Paths.get("$testDataDir/$it/kotlin").toString() + } + name = "js" + includes = listOf(Paths.get("$includesDir/include2.md").toString()) + } + sourceSet { + moduleName = "example" + analysisPlatform = "jvm" + sourceRoots = listOf("jvmMain", "commonMain", "jvmAndJsSecondCommonMain").map { + Paths.get("$testDataDir/$it/kotlin").toString() + } + name = "jvm" + includes = listOf(Paths.get("$includesDir/include1.md").toString()) + } + } + } + + testFromData(configuration) { + documentablesMergingStage = { + Assertions.assertEquals(2, it.documentation.size) + Assertions.assertEquals(2, it.packages.size) + Assertions.assertEquals(1, it.packages.first().documentation.size) + Assertions.assertEquals(1, it.packages.last().documentation.size) + } + } + + } + + @Test + fun `Sources multiplatform class documentation`() { + + val testDataDir = getTestDataDir("linkable/sources").toAbsolutePath() + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + moduleName = "example" + analysisPlatform = "js" + sourceRoots = listOf("$testDataDir/jsMain/kotlin") + sourceLinks = listOf( + SourceLinkDefinitionImpl( + path = "jsMain/kotlin", + url = "https://github.com/user/repo/tree/master/src/jsMain/kotlin", + lineSuffix = "#L" + ) + ) + name = "js" + } + sourceSet { + moduleName = "example" + analysisPlatform = "jvm" + sourceRoots = listOf("$testDataDir/jvmMain/kotlin") + sourceLinks = listOf( + SourceLinkDefinitionImpl( + path = "jvmMain/kotlin", + url = "https://github.com/user/repo/tree/master/src/jvmMain/kotlin", + lineSuffix = "#L" + ) + ) + name = "jvm" + } + } + } + + testFromData(configuration) { + renderingStage = { rootPageNode, dokkaContext -> + val newRoot = SourceLinksTransformer( + dokkaContext, + PageContentBuilder( + dokkaContext.single(dokkaContext.plugin<DokkaBase>().commentsToContentConverter), + dokkaContext.single(dokkaContext.plugin<DokkaBase>().signatureProvider), + dokkaContext.logger + ) + ).invoke(rootPageNode) + val moduleChildren = newRoot.children + Assertions.assertEquals(1, moduleChildren.size) + val packageChildren = moduleChildren.first().children + Assertions.assertEquals(2, packageChildren.size) + packageChildren.forEach { + val name = it.name.substringBefore("Class") + val crl = it.safeAs<ClasslikePageNode>()?.content?.safeAs<ContentGroup>()?.children?.last() + ?.safeAs<ContentGroup>()?.children?.last()?.safeAs<ContentGroup>()?.children?.lastOrNull() + ?.safeAs<ContentTable>()?.children?.singleOrNull() + ?.safeAs<ContentGroup>()?.children?.singleOrNull().safeAs<ContentResolvedLink>() + Assertions.assertEquals( + "https://github.com/user/repo/tree/master/src/${name.toLowerCase()}Main/kotlin/${name}Class.kt#L3", + crl?.address + ) + } + } + } + } + + @Test + fun `Samples multiplatform documentation`() { + + val testDataDir = getTestDataDir("linkable/samples").toAbsolutePath() + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + moduleName = "example" + analysisPlatform = "js" + sourceRoots = listOf("$testDataDir/jsMain/kotlin") + name = "js" + samples = listOf("$testDataDir/jsMain/resources/Samples.kt") + } + sourceSet { + moduleName = "example" + analysisPlatform = "jvm" + sourceRoots = listOf("$testDataDir/jvmMain/kotlin") + name = "jvm" + samples = listOf("$testDataDir/jvmMain/resources/Samples.kt") + } + } + } + + testFromData(configuration) { + renderingStage = { rootPageNode, dokkaContext -> + val newRoot = DefaultSamplesTransformer(dokkaContext).invoke(rootPageNode) + + val moduleChildren = newRoot.children + Assertions.assertEquals(1, moduleChildren.size) + val packageChildren = moduleChildren.first().children + Assertions.assertEquals(2, packageChildren.size) + packageChildren.forEach { + val name = it.name.substringBefore("Class") + val classChildren = it.children + Assertions.assertEquals(2, classChildren.size) + val function = classChildren.find { it.name == "printWithExclamation" } + val text = function.cast<MemberPageNode>().content.cast<ContentGroup>().children.last() + .cast<ContentDivergentGroup>().children.single() + .cast<ContentDivergentInstance>().before + .cast<ContentGroup>().children.last() + .cast<ContentGroup>().children.last() + .cast<PlatformHintedContent>().children.single() + .cast<ContentGroup>().children.single() + .cast<ContentGroup>().children.single() + .cast<ContentCodeBlock>().children.single().cast<ContentText>().text + Assertions.assertEquals( + """|import p2.${name}Class + |fun main() { + | //sampleStart + | ${name}Class().printWithExclamation("Hi, $name") + | //sampleEnd + |}""".trimMargin(), + text + ) + } + } + } + } + + @Test + fun `Documenting return type for a function in inner class with generic parent`() { + testInline( + """ + |/src/main/kotlin/test/source.kt + |package test + | + |class Sample<S>(first: S){ + | inner class SampleInner { + | fun foo(): S = TODO() + | } + |} + | + """.trimIndent(), + dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = "jvm" + name = "js" + } + } + } + ) { + renderingStage = { module, _ -> + val sample = module.children.single { it.name == "test" } + .children.single { it.name == "Sample" }.cast<ClasslikePageNode>() + val foo = sample + .children.single { it.name == "SampleInner" }.cast<ClasslikePageNode>() + .children.single { it.name == "foo" }.cast<MemberPageNode>() + + val returnTypeNode = foo.content.dfs { + val link = it.safeAs<ContentDRILink>()?.children + val child = link?.first().safeAs<ContentText>() + child?.text == "S" + }?.safeAs<ContentDRILink>() + + Assertions.assertEquals(sample.dri.first(), returnTypeNode?.address) + } + } + } +} diff --git a/plugins/base/src/test/kotlin/locationProvider/DefaultLocationProviderTest.kt b/plugins/base/src/test/kotlin/locationProvider/DefaultLocationProviderTest.kt new file mode 100644 index 00000000..a219fb04 --- /dev/null +++ b/plugins/base/src/test/kotlin/locationProvider/DefaultLocationProviderTest.kt @@ -0,0 +1,41 @@ +package locationProvider + +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.base.resolvers.local.DefaultLocationProvider +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Test + +class DefaultLocationProviderTest: AbstractCoreTest() { + @Test + fun `#644 same directory for module and package`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + + testInline( + """ + |/src/main/kotlin/basic/Test.kt + | + |class Test { + | val x = 1 + |} + """.trimMargin(), + configuration + ) { + var context: DokkaContext? = null + pluginsSetupStage = { + context = it + } + + pagesGenerationStage = { module -> + val lp = DefaultLocationProvider(module, context!!) + assertNotEquals(lp.resolve(module.children.single()).removePrefix("/"), lp.resolve(module)) + } + } + } +} diff --git a/plugins/base/src/test/kotlin/markdown/KDocTest.kt b/plugins/base/src/test/kotlin/markdown/KDocTest.kt new file mode 100644 index 00000000..f5b29322 --- /dev/null +++ b/plugins/base/src/test/kotlin/markdown/KDocTest.kt @@ -0,0 +1,47 @@ +package markdown + +import org.jetbrains.dokka.model.DPackage +import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.dokka.pages.ModulePageNode +import org.junit.jupiter.api.Assertions.* +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest + +abstract class KDocTest : AbstractCoreTest() { + + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/example/Test.kt") + } + } + } + + private fun interpolateKdoc(kdoc: String) = """ + |/src/main/kotlin/example/Test.kt + |package example + | /** + ${kdoc.split("\n").joinToString("") { "| *$it\n" } } + | */ + |class Test + """.trimMargin() + + private fun actualDocumentationNode(modulePageNode: ModulePageNode) = + (modulePageNode.documentable?.children?.first() as DPackage) + .classlikes.single() + .documentation.values.single() + + + protected fun executeTest(kdoc: String, expectedDocumentationNode: DocumentationNode) { + testInline( + interpolateKdoc(kdoc), + configuration + ) { + pagesGenerationStage = { + assertEquals( + expectedDocumentationNode, + actualDocumentationNode(it as ModulePageNode) + ) + } + } + } +} diff --git a/plugins/base/src/test/kotlin/markdown/LinkTest.kt b/plugins/base/src/test/kotlin/markdown/LinkTest.kt new file mode 100644 index 00000000..a6333c5a --- /dev/null +++ b/plugins/base/src/test/kotlin/markdown/LinkTest.kt @@ -0,0 +1,79 @@ +package markdown + +import org.jetbrains.dokka.pages.ClasslikePageNode +import org.jetbrains.dokka.pages.ContentDRILink +import org.jetbrains.dokka.pages.MemberPageNode +import org.jetbrains.dokka.pages.dfs +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test + +class LinkTest : AbstractCoreTest() { + @Test + fun linkToClassLoader() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/parser") + } + } + } + testInline( + """ + |/src/main/kotlin/parser/Test.kt + |package parser + | + | /** + | * Some docs that link to [ClassLoader.clearAssertionStatus] + | */ + |fun test(x: ClassLoader) = x.clearAssertionStatus() + | + """.trimMargin(), + configuration + ) { + renderingStage = { rootPageNode, _ -> + assertNotNull((rootPageNode.children.single().children.single() as MemberPageNode) + .content + .dfs { node -> + node is ContentDRILink && + node.address.toString() == "parser//test/#java.lang.ClassLoader/PointingToDeclaration/"} + ) + } + } + } + + @Test + fun returnTypeShouldHaveLinkToOuterClassFromInner() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin") + displayName = "JVM" + } + } + } + //This does not contain a package to check for situation when the package has to be artificially generated + testInline( + """ + |/src/main/kotlin/parser/Test.kt + | + |class Outer<OUTER> { + | inner class Inner<INNER> { + | fun foo(): OUTER = TODO() + | } + |} + """.trimMargin(), + configuration + ) { + renderingStage = { rootPageNode, _ -> + val root = rootPageNode.children.single().children.single() as ClasslikePageNode + val innerClass = root.children.first { it is ClasslikePageNode } + val foo = innerClass.children.first { it.name == "foo" } as MemberPageNode + + assertEquals(root.dri.first().toString(), "[JVM root]/Outer///PointingToDeclaration/") + assertNotNull(foo.content.dfs { it is ContentDRILink && it.address.toString() == root.dri.first().toString() } ) + } + } + } +} diff --git a/plugins/base/src/test/kotlin/markdown/ParserTest.kt b/plugins/base/src/test/kotlin/markdown/ParserTest.kt new file mode 100644 index 00000000..ee6170c2 --- /dev/null +++ b/plugins/base/src/test/kotlin/markdown/ParserTest.kt @@ -0,0 +1,1123 @@ +package org.jetbrains.dokka.tests + +import markdown.KDocTest +import org.jetbrains.dokka.model.doc.* +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test + + +class ParserTest : KDocTest() { + + @Test + fun `Simple text`() { + val kdoc = """ + | This is simple test of string + | Next line + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(Text("This is simple test of string Next line"))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Simple text with new line`() { + val kdoc = """ + | This is simple test of string\ + | Next line + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + Text("This is simple test of string"), + Br, + Text("Next line") + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Text with Bold and Emphasis decorators`() { + val kdoc = """ + | This is **simple** test of _string_ + | Next **_line_** + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + Text("This is "), + B(listOf(Text("simple"))), + Text(" test of "), + I(listOf(Text("string"))), + Text(" Next "), + B(listOf(I(listOf(Text("line"))))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Text with Colon`() { + val kdoc = """ + | This is simple text with: colon! + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(Text("This is simple text with: colon!"))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Multilined text`() { + val kdoc = """ + | Text + | and + | String + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(Text("Text and String"))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Paragraphs`() { + val kdoc = """ + | Paragraph number + | one + | + | Paragraph\ + | number two + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + P(listOf(Text("Paragraph number one"))), + P(listOf(Text("Paragraph"), Br, Text("number two"))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Emphasis with star`() { + val kdoc = " *text*" + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(I(listOf(Text("text"))))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Underscores that are not Emphasis`() { + val kdoc = "text_with_underscores" + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(Text("text_with_underscores"))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Emphasis with underscores`() { + val kdoc = "_text_" + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(I(listOf(Text("text"))))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Embedded star`() { + val kdoc = "Embedded*Star" + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P(listOf(Text("Embedded*Star"))) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + + @Test + fun `Unordered list`() { + val kdoc = """ + | * list item 1 + | * list item 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ul( + listOf( + Li(listOf(P(listOf(Text("list item 1"))))), + Li(listOf(P(listOf(Text("list item 2"))))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Unordered list with multilines`() { + val kdoc = """ + | * list item 1 + | continue 1 + | * list item 2\ + | continue 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ul( + listOf( + Li(listOf(P(listOf(Text("list item 1 continue 1"))))), + Li(listOf(P(listOf(Text("list item 2"), Br, Text("continue 2"))))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Unordered list with Bold`() { + val kdoc = """ + | * list **item** 1 + | continue 1 + | * list __item__ 2 + | continue 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ul( + listOf( + Li( + listOf( + P( + listOf( + Text("list "), + B(listOf(Text("item"))), + Text(" 1 continue 1") + ) + ) + ) + ), + Li( + listOf( + P( + listOf( + Text("list "), + B(listOf(Text("item"))), + Text(" 2 continue 2") + ) + ) + ) + ) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Unordered list with nested bullets`() { + val kdoc = """ + | * Outer first + | Outer next line + | * Outer second + | - Middle first + | Middle next line + | - Middle second + | + Inner first + | Inner next line + | - Middle third + | * Outer third + | + | New paragraph""".trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + Ul( + listOf( + Li(listOf(P(listOf(Text("Outer first Outer next line"))))), + Li(listOf(P(listOf(Text("Outer second"))))), + Ul( + listOf( + Li(listOf(P(listOf(Text("Middle first Middle next line"))))), + Li(listOf(P(listOf(Text("Middle second"))))), + Ul( + listOf( + Li(listOf(P(listOf(Text("Inner first Inner next line"))))) + ) + ), + Li(listOf(P(listOf(Text("Middle third"))))) + ) + ), + Li(listOf(P(listOf(Text("Outer third"))))) + ) + ), + P(listOf(Text("New paragraph"))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Ordered list`() { + val kdoc = """ + | 1. list item 1 + | 2. list item 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ol( + listOf( + Li(listOf(P(listOf(Text("list item 1"))))), + Li(listOf(P(listOf(Text("list item 2"))))) + ), + mapOf("start" to "1") + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + + @Test + fun `Ordered list beginning from other number`() { + val kdoc = """ + | 9. list item 1 + | 12. list item 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ol( + listOf( + Li(listOf(P(listOf(Text("list item 1"))))), + Li(listOf(P(listOf(Text("list item 2"))))) + ), + mapOf("start" to "9") + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Ordered list with multilines`() { + val kdoc = """ + | 2. list item 1 + | continue 1 + | 3. list item 2 + | continue 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ol( + listOf( + Li(listOf(P(listOf(Text("list item 1 continue 1"))))), + Li(listOf(P(listOf(Text("list item 2 continue 2"))))) + ), + mapOf("start" to "2") + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Ordered list with Bold`() { + val kdoc = """ + | 1. list **item** 1 + | continue 1 + | 2. list __item__ 2 + | continue 2 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + Ol( + listOf( + Li( + listOf( + P( + listOf( + Text("list "), + B(listOf(Text("item"))), + Text(" 1 continue 1") + ) + ) + ) + ), + Li( + listOf( + P( + listOf( + Text("list "), + B(listOf(Text("item"))), + Text(" 2 continue 2") + ) + ) + ) + ) + ), + mapOf("start" to "1") + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Ordered list with nested bullets`() { + val kdoc = """ + | 1. Outer first + | Outer next line + | 2. Outer second + | 1. Middle first + | Middle next line + | 2. Middle second + | 1. Inner first + | Inner next line + | 5. Middle third + | 4. Outer third + | + | New paragraph""".trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + Ol( + listOf( + Li(listOf(P(listOf(Text("Outer first Outer next line"))))), + Li(listOf(P(listOf(Text("Outer second"))))), + Ol( + listOf( + Li(listOf(P(listOf(Text("Middle first Middle next line"))))), + Li(listOf(P(listOf(Text("Middle second"))))), + Ol( + listOf( + Li(listOf(P(listOf(Text("Inner first Inner next line"))))) + ), + mapOf("start" to "1") + ), + Li(listOf(P(listOf(Text("Middle third"))))) + ), + mapOf("start" to "1") + ), + Li(listOf(P(listOf(Text("Outer third"))))) + ), + mapOf("start" to "1") + ), + P(listOf(Text("New paragraph"))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Ordered nested in Unordered nested in Ordered list`() { + val kdoc = """ + | 1. Outer first + | Outer next line + | 2. Outer second + | + Middle first + | Middle next line + | + Middle second + | 1. Inner first + | Inner next line + | + Middle third + | 4. Outer third + | + | New paragraph""".trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + Ol( + listOf( + Li(listOf(P(listOf(Text("Outer first Outer next line"))))), + Li(listOf(P(listOf(Text("Outer second"))))), + Ul( + listOf( + Li(listOf(P(listOf(Text("Middle first Middle next line"))))), + Li(listOf(P(listOf(Text("Middle second"))))), + Ol( + listOf( + Li(listOf(P(listOf(Text("Inner first Inner next line"))))) + ), + mapOf("start" to "1") + ), + Li(listOf(P(listOf(Text("Middle third"))))) + ) + ), + Li(listOf(P(listOf(Text("Outer third"))))) + ), + mapOf("start" to "1") + ), + P(listOf(Text("New paragraph"))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Header and two paragraphs`() { + val kdoc = """ + | # Header 1 + | Following text + | + | New paragraph + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + H1(listOf(Text("Header 1"))), + P(listOf(Text("Following text"))), + P(listOf(Text("New paragraph"))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Disabled //TODO: ATX_2 to ATX_6 and sometimes ATX_1 from jetbrains parser consumes white space. Need to handle it in their library + @Test + fun `All headers`() { + val kdoc = """ + | # Header 1 + | Text 1 + | ## Header 2 + | Text 2 + | ### Header 3 + | Text 3 + | #### Header 4 + | Text 4 + | ##### Header 5 + | Text 5 + | ###### Header 6 + | Text 6 + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + H1(listOf(Text("Header 1"))), + P(listOf(Text("Text 1"))), + H2(listOf(Text("Header 2"))), + P(listOf(Text("Text 2"))), + H3(listOf(Text("Header 3"))), + P(listOf(Text("Text 3"))), + H4(listOf(Text("Header 4"))), + P(listOf(Text("Text 4"))), + H5(listOf(Text("Header 5"))), + P(listOf(Text("Text 5"))), + H6(listOf(Text("Header 6"))), + P(listOf(Text("Text 6"))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Bold New Line Bold`() { + val kdoc = """ + | **line 1**\ + | **line 2** + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + B(listOf(Text("line 1"))), + Br, + B(listOf(Text("line 2"))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Horizontal rule`() { + val kdoc = """ + | *** + | text 1 + | ___ + | text 2 + | *** + | text 3 + | ___ + | text 4 + | *** + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + HorizontalRule, + P(listOf(Text("text 1"))), + HorizontalRule, + P(listOf(Text("text 2"))), + HorizontalRule, + P(listOf(Text("text 3"))), + HorizontalRule, + P(listOf(Text("text 4"))), + HorizontalRule + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Blockquote`() { + val kdoc = """ + | > Blockquotes are very handy in email to emulate reply text. + | > This line is part of the same quote. + | + | Quote break. + | + | > Quote + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + BlockQuote( + listOf( + P( + listOf( + Text("Blockquotes are very handy in email to emulate reply text. This line is part of the same quote.") + ) + ) + ) + ), + P(listOf(Text("Quote break."))), + BlockQuote( + listOf( + P(listOf(Text("Quote"))) + ) + ) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + + @Test + fun `Blockquote nested`() { + val kdoc = """ + | > text 1 + | > text 2 + | >> text 3 + | >> text 4 + | > + | > text 5 + | + | Quote break. + | + | > Quote + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + BlockQuote( + listOf( + P(listOf(Text("text 1 text 2"))), + BlockQuote( + listOf( + P(listOf(Text("text 3 text 4"))) + ) + ), + P(listOf(Text("text 5"))) + ) + ), + P(listOf(Text("Quote break."))), + BlockQuote( + listOf( + P(listOf(Text("Quote"))) + ) + ) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Disabled //TODO: Again ATX_1 consumes white space + @Test + fun `Blockquote nested with fancy text enhancement`() { + val kdoc = """ + | > text **1** + | > text 2 + | >> # text 3 + | >> * text 4 + | >> * text 5 + | > + | > text 6 + | + | Quote break. + | + | > Quote + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + BlockQuote( + listOf( + P( + listOf( + Text("text "), + B(listOf(Text("1"))), + Text("\ntext 2") + ) + ), + BlockQuote( + listOf( + H1(listOf(Text("text 3"))), + Ul( + listOf( + Li(listOf(P(listOf(Text("text 4"))))), + Ul( + listOf( + Li(listOf(P(listOf(Text("text 5"))))) + ) + ) + ) + ) + ) + ), + P(listOf(Text("text 6"))) + ) + ), + P(listOf(Text("Quote break."))), + BlockQuote( + listOf( + P(listOf(Text("Quote"))) + ) + ) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Simple Code Block`() { + val kdoc = """ + | `Some code` + | Sample text + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + CodeInline(listOf(Text("Some code"))), + Text(" Sample text") + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Multilined Code Block`() { + val kdoc = """ + | ```kotlin + | val x: Int = 0 + | val y: String = "Text" + | + | val z: Boolean = true + | for(i in 0..10) { + | println(i) + | } + | ``` + | Sample text + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + CodeBlock( + listOf( + Text("val x: Int = 0"), Br, + Text("val y: String = \"Text\""), Br, Br, + Text(" val z: Boolean = true"), Br, + Text("for(i in 0..10) {"), Br, + Text(" println(i)"), Br, + Text("}") + ), + mapOf("lang" to "kotlin") + ), + P(listOf(Text("Sample text"))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + + @Test + fun `Inline link`() { + val kdoc = """ + | [I'm an inline-style link](https://www.google.com) + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + A( + listOf(Text("I'm an inline-style link")), + mapOf("href" to "https://www.google.com") + ) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Inline link with title`() { + val kdoc = """ + | [I'm an inline-style link with title](https://www.google.com "Google's Homepage") + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + A( + listOf(Text("I'm an inline-style link with title")), + mapOf("href" to "https://www.google.com", "title" to "Google's Homepage") + ) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Full reference link`() { + val kdoc = """ + | [I'm a reference-style link][Arbitrary case-insensitive reference text] + | + | [arbitrary case-insensitive reference text]: https://www.mozilla.org + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + P( + listOf( + A( + listOf(Text("I'm a reference-style link")), + mapOf("href" to "https://www.mozilla.org") + ) + ) + ) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Full reference link with number`() { + val kdoc = """ + | [You can use numbers for reference-style link definitions][1] + | + | [1]: http://slashdot.org + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + P( + listOf( + A( + listOf(Text("You can use numbers for reference-style link definitions")), + mapOf("href" to "http://slashdot.org") + ) + ) + ) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Short reference link`() { + val kdoc = """ + | Or leave it empty and use the [link text itself]. + | + | [link text itself]: http://www.reddit.com + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + P( + listOf( + Text("Or leave it empty and use the "), + A( + listOf(Text("link text itself")), + mapOf("href" to "http://www.reddit.com") + ), + Text(".") + ) + ) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Autolink`() { + val kdoc = """ + | URLs and URLs in angle brackets will automatically get turned into links. + | http://www.example.com or <http://www.example.com> and sometimes + | example.com (but not on Github, for example). + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + Text("URLs and URLs in angle brackets will automatically get turned into links. http://www.example.com or "), + A( + listOf(Text("http://www.example.com")), + mapOf("href" to "http://www.example.com") + ), + Text(" and sometimes example.com (but not on Github, for example).") + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Various links`() { + val kdoc = """ + | [I'm an inline-style link](https://www.google.com) + | + | [I'm an inline-style link with title](https://www.google.com "Google's Homepage") + | + | [I'm a reference-style link][Arbitrary case-insensitive reference text] + | + | [You can use numbers for reference-style link definitions][1] + | + | Or leave it empty and use the [link text itself]. + | + | URLs and URLs in angle brackets will automatically get turned into links. + | http://www.example.com or <http://www.example.com> and sometimes + | example.com (but not on Github, for example). + | + | Some text to show that the reference links can follow later. + | + | [arbitrary case-insensitive reference text]: https://www.mozilla.org + | [1]: http://slashdot.org + | [link text itself]: http://www.reddit.com + """.trimMargin() + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + P( + listOf( + A( + listOf(Text("I'm an inline-style link")), + mapOf("href" to "https://www.google.com") + ) + ) + ), + P( + listOf( + A( + listOf(Text("I'm an inline-style link with title")), + mapOf("href" to "https://www.google.com", "title" to "Google's Homepage") + ) + ) + ), + P( + listOf( + A( + listOf(Text("I'm a reference-style link")), + mapOf("href" to "https://www.mozilla.org") + ) + ) + ), + P( + listOf( + A( + listOf(Text("You can use numbers for reference-style link definitions")), + mapOf("href" to "http://slashdot.org") + ) + ) + ), + P( + listOf( + Text("Or leave it empty and use the "), + A( + listOf(Text("link text itself")), + mapOf("href" to "http://www.reddit.com") + ), + Text(".") + ) + ), + P( + listOf( + Text("URLs and URLs in angle brackets will automatically get turned into links. http://www.example.com or "), + A( + listOf(Text("http://www.example.com")), + mapOf("href" to "http://www.example.com") + ), + Text(" and sometimes example.com (but not on Github, for example).") + ) + ), + P(listOf(Text("Some text to show that the reference links can follow later."))) + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } + + @Test + fun `Windows Carriage Return Line Feed`() { + val kdoc = "text\r\ntext" + val expectedDocumentationNode = DocumentationNode( + listOf( + Description( + P( + listOf( + Text("text text") + ) + ) + ) + ) + ) + executeTest(kdoc, expectedDocumentationNode) + } +} + diff --git a/plugins/base/src/test/kotlin/model/ClassesTest.kt b/plugins/base/src/test/kotlin/model/ClassesTest.kt new file mode 100644 index 00000000..5dc8812e --- /dev/null +++ b/plugins/base/src/test/kotlin/model/ClassesTest.kt @@ -0,0 +1,533 @@ +package model + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.sureClassNames +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.KotlinModifier.* +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import utils.AbstractModelTest +import utils.assertNotNull +import utils.name +import utils.supers + + +class ClassesTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "classes") { + + @Test + fun emptyClass() { + inlineModelTest( + """ + |class Klass {}""" + ) { + with((this / "classes" / "Klass").cast<DClass>()) { + name equals "Klass" + children counts 4 + } + } + } + + @Test + fun emptyObject() { + inlineModelTest( + """ + |object Obj {} + """ + ) { + with((this / "classes" / "Obj").cast<DObject>()) { + name equals "Obj" + children counts 3 + } + } + } + + @Test + fun classWithConstructor() { + inlineModelTest( + """ + |class Klass(name: String) + """ + ) { + with((this / "classes" / "Klass").cast<DClass>()) { + name equals "Klass" + children counts 4 + + with(constructors.firstOrNull().assertNotNull("Constructor")) { + visibility.values allEquals KotlinVisibility.Public + parameters counts 1 + with(parameters.firstOrNull().assertNotNull("Constructor parameter")) { + name equals "name" + type.name equals "String" + } + } + + } + } + } + + @Test + fun classWithFunction() { + inlineModelTest( + """ + |class Klass { + | fun fn() {} + |} + """ + ) { + with((this / "classes" / "Klass").cast<DClass>()) { + name equals "Klass" + children counts 5 + + with((this / "fn").cast<DFunction>()) { + type.name equals "Unit" + parameters counts 0 + visibility.values allEquals KotlinVisibility.Public + } + } + } + } + + @Test + fun classWithProperty() { + inlineModelTest( + """ + |class Klass { + | val name: String = "" + |} + """ + ) { + with((this / "classes" / "Klass").cast<DClass>()) { + name equals "Klass" + children counts 5 + + with((this / "name").cast<DProperty>()) { + name equals "name" + // TODO property name + } + } + } + } + + @Test + fun classWithCompanionObject() { + inlineModelTest( + """ + |class Klass() { + | companion object { + | val x = 1 + | fun foo() {} + | } + |} + """ + ) { + with((this / "classes" / "Klass").cast<DClass>()) { + name equals "Klass" + children counts 5 + + with((this / "Companion").cast<DObject>()) { + name equals "Companion" + children counts 5 + + with((this / "x").cast<DProperty>()) { + name equals "x" + } + + with((this / "foo").cast<DFunction>()) { + name equals "foo" + parameters counts 0 + type.name equals "Unit" + } + } + } + } + } + + @Test + fun dataClass() { + inlineModelTest( + """ + |data class Klass() {} + """ + ) { + with((this / "classes" / "Klass").cast<DClass>()) { + name equals "Klass" + visibility.values allEquals KotlinVisibility.Public + with(extra[AdditionalModifiers]!!.content.entries.single().value.assertNotNull("Extras")) { + this counts 1 + first() equals ExtraModifiers.KotlinOnlyModifiers.Data + } + } + } + } + + @Test + fun sealedClass() { + inlineModelTest( + """ + |sealed class Klass() {} + """ + ) { + with((this / "classes" / "Klass").cast<DClass>()) { + name equals "Klass" + modifier.values.forEach { it equals Sealed } + } + } + } + + @Test + fun annotatedClassWithAnnotationParameters() { + inlineModelTest( + """ + |@Deprecated("should no longer be used") class Foo() {} + """ + ) { + with((this / "classes" / "Foo").cast<DClass>()) { + with(extra[Annotations]!!.content.entries.single().value.assertNotNull("Annotations")) { + this counts 1 + with(first()) { + dri.classNames equals "Deprecated" + params.entries counts 1 + (params["message"].assertNotNull("message") as StringValue).value equals "\"should no longer be used\"" + } + } + } + } + } + + @Test + fun notOpenClass() { + inlineModelTest( + """ + |open class C() { + | open fun f() {} + |} + | + |class D() : C() { + | override fun f() {} + |} + """ + ) { + val C = (this / "classes" / "C").cast<DClass>() + val D = (this / "classes" / "D").cast<DClass>() + + with(C) { + modifier.values.forEach { it equals Open } + with((this / "f").cast<DFunction>()) { + modifier.values.forEach { it equals Open } + } + } + with(D) { + modifier.values.forEach { it equals Final } + with((this / "f").cast<DFunction>()) { + modifier.values.forEach { it equals Open } + } + D.supertypes.flatMap { it.component2() }.firstOrNull()?.dri equals C.dri + } + } + } + + @Test + fun indirectOverride() { + inlineModelTest( + """ + |abstract class C() { + | abstract fun foo() + |} + | + |abstract class D(): C() + | + |class E(): D() { + | override fun foo() {} + |} + """ + ) { + val C = (this / "classes" / "C").cast<DClass>() + val D = (this / "classes" / "D").cast<DClass>() + val E = (this / "classes" / "E").cast<DClass>() + + with(C) { + modifier.values.forEach { it equals Abstract } + ((this / "foo").cast<DFunction>()).modifier.values.forEach { it equals Abstract } + } + + with(D) { + modifier.values.forEach { it equals Abstract } + } + + with(E) { + modifier.values.forEach { it equals Final } + + } + D.supers.single().dri equals C.dri + E.supers.single().dri equals D.dri + } + } + + @Test + fun innerClass() { + inlineModelTest( + """ + |class C { + | inner class D {} + |} + """ + ) { + with((this / "classes" / "C").cast<DClass>()) { + + with((this / "D").cast<DClass>()) { + with(extra[AdditionalModifiers]!!.content.entries.single().value.assertNotNull("AdditionalModifiers")) { + this counts 1 + first() equals ExtraModifiers.KotlinOnlyModifiers.Inner + } + } + } + } + } + + @Test + fun companionObjectExtension() { + inlineModelTest( + """ + |class Klass { + | companion object Default {} + |} + | + |/** + | * The def + | */ + |val Klass.Default.x: Int get() = 1 + """ + ) { + with((this / "classes" / "Klass").cast<DClass>()) { + name equals "Klass" + + with((this / "Default").cast<DObject>()) { + name equals "Default" + // TODO extensions + } + } + } + } + +// @Test fun companionObjectExtension() { +// checkSourceExistsAndVerifyModel("testdata/classes/companionObjectExtension.kt", defaultModelConfig) { model -> +// val pkg = model.members.single() +// val cls = pkg.members.single { it.name == "Foo" } +// val extensions = cls.extensions.filter { it.kind == NodeKind.CompanionObjectProperty } +// assertEquals(1, extensions.size) +// } +// } + + @Test + fun secondaryConstructor() { + inlineModelTest( + """ + |class C() { + | /** This is a secondary constructor. */ + | constructor(s: String): this() {} + |} + """ + ) { + with((this / "classes" / "C").cast<DClass>()) { + name equals "C" + constructors counts 2 + + constructors.map { it.name } allEquals "<init>" + + with(constructors.find { it.parameters.isNullOrEmpty() } notNull "C()") { + parameters counts 0 + } + + with(constructors.find { it.parameters.isNotEmpty() } notNull "C(String)") { + parameters counts 1 + with(parameters.firstOrNull() notNull "Constructor parameter") { + name equals "s" + type.name equals "String" + } + } + } + } + } + + @Test + fun sinceKotlin() { + inlineModelTest( + """ + |/** + | * Useful + | */ + |@SinceKotlin("1.1") + |class C + """ + ) { + with((this / "classes" / "C").cast<DClass>()) { + with(extra[Annotations]!!.content.entries.single().value.assertNotNull("Annotations")) { + this counts 1 + with(first()) { + dri.classNames equals "SinceKotlin" + params.entries counts 1 + (params["version"].assertNotNull("version") as StringValue).value equals "\"1.1\"" + } + } + } + } + } + + @Test + fun privateCompanionObject() { + inlineModelTest( + """ + |class Klass { + | private companion object { + | fun fn() {} + | val a = 0 + | } + |} + """ + ) { + with((this / "classes" / "Klass").cast<DClass>()) { + name equals "Klass" + assertNull(companion, "Companion should not be visible by default") + } + } + } + + @Test + fun companionObject() { + inlineModelTest( + """ + |class Klass { + | companion object { + | fun fn() {} + | val a = 0 + | } + |} + """ + ) { + with((this / "classes" / "Klass").cast<DClass>()) { + name equals "Klass" + with((this / "Companion").cast<DObject>()) { + name equals "Companion" + visibility.values allEquals KotlinVisibility.Public + + with((this / "fn").cast<DFunction>()) { + name equals "fn" + parameters counts 0 + receiver equals null + } + } + } + } + } + + @Test + fun annotatedClass() { + inlineModelTest( + """@Suppress("abc") class Foo() {}""" + ) { + with((this / "classes" / "Foo").cast<DClass>()) { + with(extra[Annotations]?.content?.values?.firstOrNull()?.firstOrNull().assertNotNull("annotations")) { + dri.toString() equals "kotlin/Suppress///PointingToDeclaration/" + (params["names"].assertNotNull("param") as ArrayValue).value equals listOf(StringValue("\"abc\"")) + } + } + } + } + + @Test + fun javaAnnotationClass() { + inlineModelTest( + """ + |import java.lang.annotation.Retention + |import java.lang.annotation.RetentionPolicy + | + |@Retention(RetentionPolicy.SOURCE) + |public annotation class throws() + """ + ) { + with((this / "classes" / "throws").cast<DAnnotation>()) { + with(extra[Annotations]!!.content.entries.single().value.assertNotNull("Annotations")) { + this counts 1 + with(first()) { + dri.classNames equals "Retention" + params["value"].assertNotNull("value") equals EnumValue( + "RetentionPolicy.SOURCE", + DRI("java.lang.annotation", "RetentionPolicy.SOURCE") + ) + } + } + } + } + } + + @Test fun genericAnnotationClass() { + inlineModelTest( + """annotation class Foo<A,B,C,D:Number>() {}""" + ) { + with((this / "classes" / "Foo").cast<DAnnotation>()){ + generics.map { it.name to it.bounds.first().name } equals listOf("A" to "Any", "B" to "Any", "C" to "Any", "D" to "Number") + } + } + } + + @Test fun nestedGenericClasses(){ + inlineModelTest( + """ + |class Outer<OUTER> { + | inner class Inner<INNER, T : OUTER> { } + |} + """.trimMargin() + ){ + with((this / "classes" / "Outer").cast<DClass>()){ + val inner = classlikes.single().cast<DClass>() + inner.generics.map { it.name to it.bounds.first().name } equals listOf("INNER" to "Any", "T" to "OUTER") + } + } + } + + @Test fun allImplementedInterfaces() { + inlineModelTest( + """ + | interface Highest { } + | open class HighestImpl: Highest { } + | interface Lower { } + | interface LowerImplInterface: Lower { } + | class Tested : HighestImpl(), LowerImplInterface { } + """.trimIndent() + ){ + with((this / "classes" / "Tested").cast<DClass>()){ + extra[ImplementedInterfaces]?.interfaces?.entries?.single()?.value?.map { it.sureClassNames }?.sorted() equals listOf("Highest", "Lower", "LowerImplInterface").sorted() + } + } + } + + @Test fun multipleClassInheritance() { + inlineModelTest( + """ + | open class A { } + | open class B: A() { } + | class Tested : B() { } + """.trimIndent() + ) { + with((this / "classes" / "Tested").cast<DClass>()) { + supertypes.entries.single().value.map { it.dri.sureClassNames }.single() equals "B" + } + } + } + + @Test fun multipleClassInheritanceWithInterface(){ + inlineModelTest( + """ + | open class A { } + | open class B: A() { } + | interface X { } + | interface Y : X { } + | class Tested : B(), Y { } + """.trimIndent() + ){ + with((this / "classes" / "Tested").cast<DClass>()) { + supertypes.entries.single().value.map { it.dri.sureClassNames to it.kind }.sortedBy { it.first } equals listOf("B" to KotlinClassKindTypes.CLASS, "Y" to KotlinClassKindTypes.INTERFACE) + } + } + } +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/model/CommentTest.kt b/plugins/base/src/test/kotlin/model/CommentTest.kt new file mode 100644 index 00000000..c1da8ee0 --- /dev/null +++ b/plugins/base/src/test/kotlin/model/CommentTest.kt @@ -0,0 +1,332 @@ +package model + +import org.jetbrains.dokka.model.DProperty +import org.jetbrains.dokka.model.doc.CustomTagWrapper +import org.jetbrains.dokka.model.doc.Text +import org.junit.jupiter.api.Test +import utils.* + +class CommentTest : AbstractModelTest("/src/main/kotlin/comment/Test.kt", "comment") { + + @Test + fun codeBlockComment() { + inlineModelTest( + """ + |/** + | * ```brainfuck + | * ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>. + | * ``` + | */ + |val prop1 = "" + | + | + |/** + | * ``` + | * a + b - c + | * ``` + | */ + |val prop2 = "" + """ + ) { + with((this / "comment" / "prop1").cast<DProperty>()) { + name equals "prop1" + with(this.docs().firstOrNull()?.root.assertNotNull("Code")) { + (children.firstOrNull() as? Text) + ?.body equals "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>." + + params["lang"] equals "brainfuck" + } + } + with((this / "comment" / "prop2").cast<DProperty>()) { + name equals "prop2" + comments() equals "a + b - c" + } + } + } + + @Test + fun emptyDoc() { + inlineModelTest( + """ + val property = "test" + """ + ) { + with((this / "comment" / "property").cast<DProperty>()) { + name equals "property" + comments() equals "" + } + } + } + + @Test + fun emptyDocButComment() { + inlineModelTest( + """ + |/* comment */ + |val property = "test" + |fun tst() = property + """ + ) { + val p = this + with((this / "comment" / "property").cast<DProperty>()) { + comments() equals "" + } + } + } + + @Test + fun multilineDoc() { + inlineModelTest( + """ + |/** + | * doc1 + | * + | * doc2 + | * doc3 + | */ + |val property = "test" + """ + ) { + with((this / "comment" / "property").cast<DProperty>()) { + comments() equals "doc1\ndoc2 doc3" + } + } + } + + @Test + fun multilineDocWithComment() { + inlineModelTest( + """ + |/** + | * doc1 + | * + | * doc2 + | * doc3 + | */ + |// comment + |val property = "test" + """ + ) { + with((this / "comment" / "property").cast<DProperty>()) { + comments() equals "doc1\ndoc2 doc3" + } + } + } + + @Test + fun oneLineDoc() { + inlineModelTest( + """ + |/** doc */ + |val property = "test" + """ + ) { + with((this / "comment" / "property").cast<DProperty>()) { + comments() equals "doc" + } + } + } + + @Test + fun oneLineDocWithComment() { + inlineModelTest( + """ + |/** doc */ + |// comment + |val property = "test" + """ + ) { + with((this / "comment" / "property").cast<DProperty>()) { + comments() equals "doc" + } + } + } + + @Test + fun oneLineDocWithEmptyLine() { + inlineModelTest( + """ + |/** doc */ + | + |val property = "test" + """ + ) { + with((this / "comment" / "property").cast<DProperty>()) { + comments() equals "doc" + } + } + } + + @Test + fun emptySection() { + inlineModelTest( + """ + |/** + | * Summary + | * @one + | */ + |val property = "test" + """ + ) { + with((this / "comment" / "property").cast<DProperty>()) { + comments() equals "Summary\none: []" + docs().find { it is CustomTagWrapper && it.name == "one" }.let { + with(it.assertNotNull("'one' entry")) { + root.children counts 0 + root.params.keys counts 0 + } + } + } + } + } + + @Test + fun quotes() { + inlineModelTest( + """ + |/** it's "useful" */ + |val property = "test" + """ + ) { + with((this / "comment" / "property").cast<DProperty>()) { + comments() equals """it's "useful"""" + } + } + } + + @Test + fun section1() { + inlineModelTest( + """ + |/** + | * Summary + | * @one section one + | */ + |val property = "test" + """ + ) { + with((this / "comment" / "property").cast<DProperty>()) { + comments() equals "Summary\none: [section one]" + } + } + } + + + @Test + fun section2() { + inlineModelTest( + """ + |/** + | * Summary + | * @one section one + | * @two section two + | */ + |val property = "test" + """ + ) { + with((this / "comment" / "property").cast<DProperty>()) { + comments() equals "Summary\none: [section one]\ntwo: [section two]" + } + } + } + + @Test + fun multilineSection() { + inlineModelTest( + """ + |/** + | * Summary + | * @one + | * line one + | * line two + | */ + |val property = "test" + """ + ) { + with((this / "comment" / "property").cast<DProperty>()) { + comments() equals "Summary\none: [line one line two]" + } + } + } + +// @Test todo + fun directive() { + inlineModelTest( + """ + |/** + | * Summary + | * + | * @sample example1 + | * @sample example2 + | * @sample X.example3 + | * @sample X.Y.example4 + | */ + |val property = "test" + | + |fun example1(node: String) = if (true) { + | println(property) + |} + | + |fun example2(node: String) { + | if (true) { + | println(property) + | } + |} + | + |class X { + | fun example3(node: String) { + | if (true) { + | println(property) + | } + | } + | + | class Y { + | fun example4(node: String) { + | if (true) { + | println(property) + | } + | } + | } + |} + """ + ) { + with((this / "comment" / "property").cast<DProperty>()) { + this + } + } + } + + +// @Test fun directive() { +// checkSourceExistsAndVerifyModel("testdata/comments/directive.kt", defaultModelConfig) { model -> +// with(model.members.single().members.first()) { +// assertEquals("Summary", content.summary.toTestString()) +// with (content.description) { +// assertEqualsIgnoringSeparators(""" +// |[code lang=kotlin] +// |if (true) { +// | println(property) +// |} +// |[/code] +// |[code lang=kotlin] +// |if (true) { +// | println(property) +// |} +// |[/code] +// |[code lang=kotlin] +// |if (true) { +// | println(property) +// |} +// |[/code] +// |[code lang=kotlin] +// |if (true) { +// | println(property) +// |} +// |[/code] +// |""".trimMargin(), toTestString()) +// } +// } +// } +// } + +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/model/FunctionsTest.kt b/plugins/base/src/test/kotlin/model/FunctionsTest.kt new file mode 100644 index 00000000..c96e7df6 --- /dev/null +++ b/plugins/base/src/test/kotlin/model/FunctionsTest.kt @@ -0,0 +1,396 @@ +package model + +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.* +import org.junit.jupiter.api.Test +import utils.AbstractModelTest +import utils.assertNotNull +import utils.comments +import utils.name + +class FunctionTest : AbstractModelTest("/src/main/kotlin/function/Test.kt", "function") { + + @Test + fun function() { + inlineModelTest( + """ + |/** + | * Function fn + | */ + |fun fn() {} + """ + ) { + with((this / "function" / "fn").cast<DFunction>()) { + name equals "fn" + type.name equals "Unit" + this.children.assertCount(0, "Function children: ") + } + } + } + + @Test + fun overloads() { + inlineModelTest( + """ + |/** + | * Function fn + | */ + |fun fn() {} + | /** + | * Function fn(Int) + | */ + |fun fn(i: Int) {} + """ + ) { + with((this / "function").cast<DPackage>()) { + val fn1 = functions.find { + it.name == "fn" && it.parameters.isNullOrEmpty() + }.assertNotNull("fn()") + val fn2 = functions.find { + it.name == "fn" && it.parameters.isNotEmpty() + }.assertNotNull("fn(Int)") + + with(fn1) { + name equals "fn" + parameters.assertCount(0) + } + + with(fn2) { + name equals "fn" + parameters.assertCount(1) + parameters.first().type.name equals "Int" + } + } + } + } + + @Test + fun functionWithReceiver() { + inlineModelTest( + """ + |/** + | * Function with receiver + | */ + |fun String.fn() {} + | + |/** + | * Function with receiver + | */ + |fun String.fn(x: Int) {} + """ + ) { + with((this / "function").cast<DPackage>()) { + val fn1 = functions.find { + it.name == "fn" && it.parameters.isNullOrEmpty() + }.assertNotNull("fn()") + val fn2 = functions.find { + it.name == "fn" && it.parameters.count() == 1 + }.assertNotNull("fn(Int)") + + with(fn1) { + name equals "fn" + parameters counts 0 + receiver.assertNotNull("fn() receiver") + } + + with(fn2) { + name equals "fn" + parameters counts 1 + receiver.assertNotNull("fn(Int) receiver") + parameters.first().type.name equals "Int" + } + } + } + } + + @Test + fun functionWithParams() { + inlineModelTest( + """ + |/** + | * Multiline + | * + | * Function + | * Documentation + | */ + |fun function(/** parameter */ x: Int) { + |} + """ + ) { + with((this / "function" / "function").cast<DFunction>()) { + comments() equals "Multiline\nFunction Documentation" + + name equals "function" + parameters counts 1 + parameters.firstOrNull().assertNotNull("Parameter: ").also { + it.name equals "x" + it.type.name equals "Int" + it.comments() equals "parameter" + } + + type.assertNotNull("Return type: ").name equals "Unit" + } + } + } + + @Test + fun functionWithNotDocumentedAnnotation() { + inlineModelTest( + """ + |@Suppress("FOO") fun f() {} + """ + ) { + with((this / "function" / "f").cast<DFunction>()) { + with(extra[Annotations]!!.content.entries.single().value.assertNotNull("Annotations")) { + this counts 1 + with(first()) { + dri.classNames equals "Suppress" + params.entries counts 1 + (params["names"].assertNotNull("param") as ArrayValue).value equals listOf(StringValue("\"FOO\"")) + } + } + } + } + } + + @Test + fun inlineFunction() { + inlineModelTest( + """ + |inline fun f(a: () -> String) {} + """ + ) { + with((this / "function" / "f").cast<DFunction>()) { + extra[AdditionalModifiers]!!.content.entries.single().value counts 1 + extra[AdditionalModifiers]!!.content.entries.single().value exists ExtraModifiers.KotlinOnlyModifiers.Inline + } + } + } + + @Test + fun suspendFunction() { + inlineModelTest( + """ + |suspend fun f() {} + """ + ) { + with((this / "function" / "f").cast<DFunction>()) { + extra[AdditionalModifiers]!!.content.entries.single().value counts 1 + extra[AdditionalModifiers]!!.content.entries.single().value exists ExtraModifiers.KotlinOnlyModifiers.Suspend + } + } + } + + @Test + fun suspendInlineFunctionOrder() { + inlineModelTest( + """ + |suspend inline fun f(a: () -> String) {} + """ + ) { + with((this / "function" / "f").cast<DFunction>()) { + extra[AdditionalModifiers]!!.content.entries.single().value counts 2 + extra[AdditionalModifiers]!!.content.entries.single().value exists ExtraModifiers.KotlinOnlyModifiers.Suspend + extra[AdditionalModifiers]!!.content.entries.single().value exists ExtraModifiers.KotlinOnlyModifiers.Inline + } + } + } + + @Test + fun inlineSuspendFunctionOrderChanged() { + inlineModelTest( + """ + |inline suspend fun f(a: () -> String) {} + """ + ) { + with((this / "function" / "f").cast<DFunction>()) { + with(extra[AdditionalModifiers]!!.content.entries.single().value.assertNotNull("AdditionalModifiers")) { + this counts 2 + this exists ExtraModifiers.KotlinOnlyModifiers.Suspend + this exists ExtraModifiers.KotlinOnlyModifiers.Inline + } + } + } + } + + @Test + fun functionWithAnnotatedParam() { + inlineModelTest( + """ + |@Target(AnnotationTarget.VALUE_PARAMETER) + |@Retention(AnnotationRetention.SOURCE) + |@MustBeDocumented + |public annotation class Fancy + | + |fun function(@Fancy notInlined: () -> Unit) {} + """ + ) { + with((this / "function" / "Fancy").cast<DAnnotation>()) { + with(extra[Annotations]!!.content.entries.single().value.assertNotNull("Annotations")) { + this counts 3 + with(map { it.dri.classNames to it }.toMap()) { + with(this["Target"].assertNotNull("Target")) { + (params["allowedTargets"].assertNotNull("allowedTargets") as ArrayValue).value equals listOf( + EnumValue( + "AnnotationTarget.VALUE_PARAMETER", + DRI("kotlin.annotation", "AnnotationTarget.VALUE_PARAMETER") + ) + ) + } + with(this["Retention"].assertNotNull("Retention")) { + (params["value"].assertNotNull("value") as EnumValue) equals EnumValue( + "AnnotationRetention.SOURCE", + DRI("kotlin.annotation", "AnnotationRetention.SOURCE") + ) + } + this["MustBeDocumented"].assertNotNull("MustBeDocumented").params.entries counts 0 + } + } + + } + with((this / "function" / "function" / "notInlined").cast<DParameter>()) { + with(this.extra[Annotations]!!.content.entries.single().value.assertNotNull("Annotations")) { + this counts 1 + with(first()) { + dri.classNames equals "Fancy" + params.entries counts 0 + } + } + } + } + } + + @Test + fun functionWithNoinlineParam() { + inlineModelTest( + """ + |fun f(noinline notInlined: () -> Unit) {} + """ + ) { + with((this / "function" / "f" / "notInlined").cast<DParameter>()) { + extra[AdditionalModifiers]!!.content.entries.single().value counts 1 + extra[AdditionalModifiers]!!.content.entries.single().value exists ExtraModifiers.KotlinOnlyModifiers.NoInline + } + } + } + + @Test + fun annotatedFunctionWithAnnotationParameters() { + inlineModelTest( + """ + |@Target(AnnotationTarget.VALUE_PARAMETER) + |@Retention(AnnotationRetention.SOURCE) + |@MustBeDocumented + |public annotation class Fancy(val size: Int) + | + |@Fancy(1) fun f() {} + """ + ) { + with((this / "function" / "Fancy").cast<DAnnotation>()) { + constructors counts 1 + with(constructors.first()) { + parameters counts 1 + with(parameters.first()) { + type.name equals "Int" + name equals "size" + } + } + + with(extra[Annotations]!!.content.entries.single().value.assertNotNull("Annotations")) { + this counts 3 + with(map { it.dri.classNames to it }.toMap()) { + with(this["Target"].assertNotNull("Target")) { + (params["allowedTargets"].assertNotNull("allowedTargets") as ArrayValue).value equals listOf( + EnumValue( + "AnnotationTarget.VALUE_PARAMETER", + DRI("kotlin.annotation", "AnnotationTarget.VALUE_PARAMETER") + ) + ) + } + with(this["Retention"].assertNotNull("Retention")) { + (params["value"].assertNotNull("value") as EnumValue) equals EnumValue( + "AnnotationRetention.SOURCE", + DRI("kotlin.annotation", "AnnotationRetention.SOURCE") + ) + } + this["MustBeDocumented"].assertNotNull("MustBeDocumented").params.entries counts 0 + } + } + + } + with((this / "function" / "f").cast<DFunction>()) { + with(this.extra[Annotations]!!.content.entries.single().value.assertNotNull("Annotations")) { + this counts 1 + with(this.first()) { + dri.classNames equals "Fancy" + params.entries counts 1 + (params["size"] as StringValue).value equals "1" + } + } + } + } + } + + @Test + fun functionWithDefaultStringParameter() { + inlineModelTest( + """ + |/src/main/kotlin/function/Test.kt + |package function + |fun f(x: String = "") {} + """ + ) { + with((this / "function" / "f").cast<DFunction>()) { + parameters.forEach { p -> + p.name equals "x" + p.type.name.assertNotNull("Parameter type: ") equals "String" + p.extra[DefaultValue]?.value equals "\"\"" + } + } + } + } + + @Test + fun functionWithDefaultFloatParameter() { + inlineModelTest( + """ + |/src/main/kotlin/function/Test.kt + |package function + |fun f(x: Float = 3.14f) {} + """ + ) { + with((this / "function" / "f").cast<DFunction>()) { + parameters.forEach { p -> + p.name equals "x" + p.type.name.assertNotNull("Parameter type: ") equals "Float" + p.extra[DefaultValue]?.value equals "3.14f" + } + } + } + } + + @Test + fun sinceKotlin() { + inlineModelTest( + """ + |/** + | * Quite useful [String] + | */ + |@SinceKotlin("1.1") + |fun f(): String = "1.1 rulezz" + """ + ) { + with((this / "function" / "f").cast<DFunction>()) { + with(extra[Annotations]!!.content.entries.single().value.assertNotNull("Annotations")) { + this counts 1 + with(first()) { + dri.classNames equals "SinceKotlin" + params.entries counts 1 + (params["version"].assertNotNull("version") as StringValue).value equals "\"1.1\"" + } + } + } + } + } + +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/model/InheritorsTest.kt b/plugins/base/src/test/kotlin/model/InheritorsTest.kt new file mode 100644 index 00000000..503cf50c --- /dev/null +++ b/plugins/base/src/test/kotlin/model/InheritorsTest.kt @@ -0,0 +1,95 @@ +package model + +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.transformers.documentables.InheritorsExtractorTransformer +import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo +import org.jetbrains.dokka.model.DInterface +import org.jetbrains.dokka.plugability.DokkaPlugin +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import utils.AbstractModelTest +import utils.assertNotNull + +class InheritorsTest : AbstractModelTest("/src/main/kotlin/inheritors/Test.kt", "inheritors") { + + object InheritorsPlugin : DokkaPlugin() { + val inheritors by extending { + CoreExtensions.documentableTransformer with InheritorsExtractorTransformer() + } + } + + @Disabled("reenable after fixing subtypes") + @Test + fun simple() { + inlineModelTest( + """|interface A{} + |class B() : A {} + """.trimMargin(), + pluginsOverrides = listOf(InheritorsPlugin) + ) { + with((this / "inheritors" / "A").cast<DInterface>()) { + val map = extra[InheritorsInfo].assertNotNull("InheritorsInfo").value + with(map.keys.also { it counts 1 }.find { it.analysisPlatform == Platform.jvm }.assertNotNull("jvm key").let { map[it]!! } + ) { + this counts 1 + first().classNames equals "B" + } + } + } + } + + @Disabled("reenable after fixing subtypes") + @Test + fun multiplatform() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("common/src/", "jvm/src/") + analysisPlatform = "jvm" + } + sourceSet { + sourceRoots = listOf("common/src/", "js/src/") + analysisPlatform = "js" + } + } + } + + testInline( + """ + |/common/src/main/kotlin/inheritors/Test.kt + |package inheritors + |interface A{} + |/jvm/src/main/kotlin/inheritors/Test.kt + |package inheritors + |class B() : A {} + |/js/src/main/kotlin/inheritors/Test.kt + |package inheritors + |class B() : A {} + |class C() : A {} + """.trimMargin(), + configuration, + cleanupOutput = false, + pluginOverrides = listOf(InheritorsPlugin) + ) { + documentablesTransformationStage = { m -> + with((m / "inheritors" / "A").cast<DInterface>()) { + val map = extra[InheritorsInfo].assertNotNull("InheritorsInfo").value + with(map.keys.also { it counts 2 }) { + with(find { it.analysisPlatform == Platform.jvm }.assertNotNull("jvm key").let { map[it]!! }) { + this counts 1 + first().classNames equals "B" + } + with(find { it.analysisPlatform == Platform.js }.assertNotNull("js key").let { map[it]!! }) { + this counts 2 + val classes = listOf("B", "C") + assertTrue(all { classes.contains(it.classNames) }, "One of subclasses missing in js" ) + } + } + + } + } + } + } +} diff --git a/plugins/base/src/test/kotlin/model/JavaTest.kt b/plugins/base/src/test/kotlin/model/JavaTest.kt new file mode 100644 index 00000000..1f042304 --- /dev/null +++ b/plugins/base/src/test/kotlin/model/JavaTest.kt @@ -0,0 +1,346 @@ +package model + +import org.jetbrains.dokka.base.transformers.documentables.InheritorsInfo +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.links.sureClassNames +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.doc.Param +import org.jetbrains.dokka.model.doc.Text +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import utils.AbstractModelTest +import utils.assertNotNull +import utils.name + +class JavaTest : AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { + + @Test + fun function() { + inlineModelTest( + """ + |class Test { + | /** + | * Summary for Function + | * @param name is String parameter + | * @param value is int parameter + | */ + | public void fn(String name, int value) {} + |} + """ + ) { + with((this / "java" / "Test").cast<DClass>()) { + name equals "Test" + children counts 1 + with((this / "fn").cast<DFunction>()) { + name equals "fn" + val params = parameters.map { it.documentation.values.first().children.first() as Param } + params.mapNotNull { it.firstChildOfTypeOrNull<Text>()?.body } equals listOf("is String parameter", "is int parameter") + } + } + } + } + + @Test fun allImplementedInterfacesInJava() { + inlineModelTest( + """ + |interface Highest { } + |interface Lower extends Highest { } + |class Extendable { } + |class Tested extends Extendable implements Lower { } + """){ + with((this / "java" / "Tested").cast<DClass>()){ + extra[ImplementedInterfaces]?.interfaces?.entries?.single()?.value?.map { it.sureClassNames }?.sorted() equals listOf("Highest", "Lower").sorted() + } + } + } + + @Test fun multipleClassInheritanceWithInterface() { + inlineModelTest( + """ + |interface Highest { } + |interface Lower extends Highest { } + |class Extendable { } + |class Tested extends Extendable implements Lower { } + """){ + with((this / "java" / "Tested").cast<DClass>()) { + supertypes.entries.single().value.map { it.dri.sureClassNames to it.kind }.sortedBy { it.first } equals listOf("Extendable" to JavaClassKindTypes.CLASS, "Lower" to JavaClassKindTypes.INTERFACE) + } + } + } + + @Test // todo + fun memberWithModifiers() { + inlineModelTest( + """ + |class Test { + | /** + | * Summary for Function + | * @param name is String parameter + | * @param value is int parameter + | */ + | public void fn(String name, int value) {} + |} + """ + ) { + with((this / "java" / "Test" / "fn").cast<DFunction>()) { + this + } + } + } + + @Test + fun superClass() { + inlineModelTest( + """ + |public class Foo extends Exception implements Cloneable {} + """ + ) { + with((this / "java" / "Foo").cast<DClass>()) { + val sups = listOf("Exception", "Cloneable") + assertTrue( + sups.all { s -> supertypes.values.flatten().any { it.dri.classNames == s } }) + "Foo must extend ${sups.joinToString(", ")}" + } + } + } + + @Test + fun arrayType() { + inlineModelTest( + """ + |class Test { + | public String[] arrayToString(int[] data) { + | return null; + | } + |} + """ + ) { + with((this / "java" / "Test").cast<DClass>()) { + name equals "Test" + children counts 1 + + with((this / "arrayToString").cast<DFunction>()) { + name equals "arrayToString" + type.name equals "Array" + with(parameters.firstOrNull().assertNotNull("parameters")) { + name equals "data" + type.name equals "Array" + } + } + } + } + } + + @Test + fun typeParameter() { + inlineModelTest( + """ + |class Foo<T extends Comparable<T>> { + | public <E> E foo(); + |} + """ + ) { + with((this / "java" / "Foo").cast<DClass>()) { + generics counts 1 + } + } + } + + @Test + fun constructors() { + inlineModelTest( + """ + |class Test { + | public Test() {} + | + | public Test(String s) {} + |} + """ + ) { + with((this / "java" / "Test").cast<DClass>()) { + name equals "Test" + + constructors counts 2 + constructors.find { it.parameters.isNullOrEmpty() }.assertNotNull("Test()") + + with(constructors.find { it.parameters.isNotEmpty() }.assertNotNull("Test(String)")) { + parameters.firstOrNull()?.type?.name equals "String" + } + } + } + } + + @Test + fun innerClass() { + inlineModelTest( + """ + |class InnerClass { + | public class D {} + |} + """ + ) { + with((this / "java" / "InnerClass").cast<DClass>()) { + children counts 1 + with((this / "D").cast<DClass>()) { + name equals "D" + children counts 0 + } + } + } + } + + @Test + fun varargs() { + inlineModelTest( + """ + |class Foo { + | public void bar(String... x); + |} + """ + ) { + with((this / "java" / "Foo").cast<DClass>()) { + name equals "Foo" + children counts 1 + + with((this / "bar").cast<DFunction>()) { + name equals "bar" + with(parameters.firstOrNull().assertNotNull("parameter")) { + name equals "x" + type.name equals "Array" + } + } + } + } + } + + @Test // todo + fun fields() { + inlineModelTest( + """ + |class Test { + | public int i; + | public static final String s; + |} + """ + ) { + with((this / "java" / "Test").cast<DClass>()) { + children counts 2 + + with((this / "i").cast<DProperty>()) { + getter equals null + setter equals null + } + + with((this / "s").cast<DProperty>()) { + getter equals null + setter equals null + } + } + } + } + + @Test + fun staticMethod() { + inlineModelTest( + """ + |class C { + | public static void foo() {} + |} + """ + ) { + with((this / "java" / "C" / "foo").cast<DFunction>()) { + with(extra[AdditionalModifiers]!!.content.entries.single().value.assertNotNull("AdditionalModifiers")) { + this counts 1 + first() equals ExtraModifiers.JavaOnlyModifiers.Static + } + } + } + } + + @Test + fun annotatedAnnotation() { + inlineModelTest( + """ + |import java.lang.annotation.*; + | + |@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD}) + |public @interface Attribute { + | String value() default ""; + |} + """ + ) { + with((this / "java" / "Attribute").cast<DAnnotation>()) { + with(extra[Annotations]!!.content.entries.single().value.assertNotNull("Annotations")) { + with(single()) { + dri.classNames equals "Target" + (params["value"].assertNotNull("value") as ArrayValue).value equals listOf( + EnumValue("ElementType.FIELD", DRI("java.lang.annotation", "ElementType")), + EnumValue("ElementType.TYPE", DRI("java.lang.annotation", "ElementType")), + EnumValue("ElementType.METHOD", DRI("java.lang.annotation", "ElementType")) + ) + } + } + } + } + } + + @Test + fun javaLangObject() { + inlineModelTest( + """ + |class Test { + | public Object fn() { return null; } + |} + """ + ) { + with((this / "java" / "Test" / "fn").cast<DFunction>()) { + assertTrue(type is JavaObject) + } + } + } + + @Test + fun enumValues() { + inlineModelTest( + """ + |enum E { + | Foo + |} + """ + ) { + with((this / "java" / "E").cast<DEnum>()) { + name equals "E" + entries counts 1 + + with((this / "Foo").cast<DEnumEntry>()) { + name equals "Foo" + } + } + } + } + + @Test + fun inheritorLinks() { + inlineModelTest( + """ + |public class InheritorLinks { + | public static class Foo {} + | + | public static class Bar extends Foo {} + |} + """ + ) { + with((this / "java" / "InheritorLinks").cast<DClass>()) { + val dri = (this / "Bar").assertNotNull("Foo dri").dri + with((this / "Foo").cast<DClass>()) { + with(extra[InheritorsInfo].assertNotNull("InheritorsInfo")) { + with(value.values.flatten().distinct()) { + this counts 1 + first() equals dri + } + } + } + } + } + } +} diff --git a/plugins/base/src/test/kotlin/model/PackagesTest.kt b/plugins/base/src/test/kotlin/model/PackagesTest.kt new file mode 100644 index 00000000..86222d95 --- /dev/null +++ b/plugins/base/src/test/kotlin/model/PackagesTest.kt @@ -0,0 +1,134 @@ +package model + +import org.jetbrains.dokka.model.DPackage +import org.junit.jupiter.api.Test +import utils.AbstractModelTest + +class PackagesTest : AbstractModelTest("/src/main/kotlin/packages/Test.kt", "packages") { + + @Test + fun rootPackage() { + inlineModelTest( + """ + | + """.trimIndent(), + prependPackage = false, + configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin") + displayName = "JVM" + } + } + } + ) { + with((this / "[JVM root]").cast<DPackage>()) { + name equals "[JVM root]" + children counts 0 + } + } + } + + @Test + fun simpleNamePackage() { + inlineModelTest( + """ + |package simple + """.trimIndent(), + prependPackage = false + ) { + with((this / "simple").cast<DPackage>()) { + name equals "simple" + children counts 0 + } + } + } + + @Test + fun dottedNamePackage() { + inlineModelTest( + """ + |package dot.name + """.trimIndent(), + prependPackage = false + ) { + with((this / "dot.name").cast<DPackage>()) { + name equals "dot.name" + children counts 0 + } + } + + } + + @Test + fun multipleFiles() { + inlineModelTest( + """ + |package dot.name + |/src/main/kotlin/packages/Test2.kt + |package simple + """.trimIndent(), + prependPackage = false + ) { + children counts 2 + with((this / "dot.name").cast<DPackage>()) { + name equals "dot.name" + children counts 0 + } + with((this / "simple").cast<DPackage>()) { + name equals "simple" + children counts 0 + } + } + } + + @Test + fun multipleFilesSamePackage() { + inlineModelTest( + """ + |package simple + |/src/main/kotlin/packages/Test2.kt + |package simple + """.trimIndent(), + prependPackage = false + ) { + children counts 1 + with((this / "simple").cast<DPackage>()) { + name equals "simple" + children counts 0 + } + } + } + + @Test + fun classAtPackageLevel() { + inlineModelTest( + """ + |package simple.name + | + |class Foo {} + """.trimIndent(), + prependPackage = false + ) { + with((this / "simple.name").cast<DPackage>()) { + name equals "simple.name" + children counts 1 + } + } + } + + // todo +// @Test fun suppressAtPackageLevel() { +// verifyModel( +// ModelConfig( +// roots = arrayOf(KotlinSourceRoot("testdata/packages/classInPackage.kt", false)), +// perPackageOptions = listOf( +// PackageOptionsImpl(prefix = "simple.name", suppress = true) +// ), +// analysisPlatform = analysisPlatform +// ) +// ) { model -> +// assertEquals(0, model.members.count()) +// } +// } +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/model/PropertyTest.kt b/plugins/base/src/test/kotlin/model/PropertyTest.kt new file mode 100644 index 00000000..af952b43 --- /dev/null +++ b/plugins/base/src/test/kotlin/model/PropertyTest.kt @@ -0,0 +1,265 @@ +package model + +import org.jetbrains.dokka.model.* +import org.junit.jupiter.api.Test +import utils.AbstractModelTest +import utils.assertNotNull +import utils.name + +class PropertyTest : AbstractModelTest("/src/main/kotlin/property/Test.kt", "property") { + + @Test + fun valueProperty() { + inlineModelTest( + """ + |val property = "test"""" + ) { + with((this / "property" / "property").cast<DProperty>()) { + name equals "property" + children counts 0 + with(getter.assertNotNull("Getter")) { + type.name equals "String" + } + type.name equals "String" + } + } + } + + @Test + fun variableProperty() { + inlineModelTest( + """ + |var property = "test" + """ + ) { + with((this / "property" / "property").cast<DProperty>()) { + name equals "property" + children counts 0 + setter.assertNotNull("Setter") + with(getter.assertNotNull("Getter")) { + type.name equals "String" + } + type.name equals "String" + } + } + } + + @Test + fun valuePropertyWithGetter() { + inlineModelTest( + """ + |val property: String + | get() = "test" + """ + ) { + with((this / "property" / "property").cast<DProperty>()) { + name equals "property" + children counts 0 + with(getter.assertNotNull("Getter")) { + type.name equals "String" + } + type.name equals "String" + } + } + } + + @Test + fun variablePropertyWithAccessors() { + inlineModelTest( + """ + |var property: String + | get() = "test" + | set(value) {} + """ + ) { + with((this / "property" / "property").cast<DProperty>()) { + name equals "property" + children counts 0 + setter.assertNotNull("Setter") + with(getter.assertNotNull("Getter")) { + type.name equals "String" + } + visibility.values allEquals KotlinVisibility.Public + } + } + } + + @Test + fun propertyWithReceiver() { + inlineModelTest( + """ + |val String.property: Int + | get() = size() * 2 + """ + ) { + with((this / "property" / "property").cast<DProperty>()) { + name equals "property" + children counts 0 + with(receiver.assertNotNull("property receiver")) { + name equals null + type.name equals "String" + } + with(getter.assertNotNull("Getter")) { + type.name equals "Int" + } + visibility.values allEquals KotlinVisibility.Public + } + } + } + + @Test + fun propertyOverride() { + inlineModelTest( + """ + |open class Foo() { + | open val property: Int get() = 0 + |} + |class Bar(): Foo() { + | override val property: Int get() = 1 + |} + """ + ) { + with((this / "property").cast<DPackage>()) { + with((this / "Foo" / "property").cast<DProperty>()) { + name equals "property" + children counts 0 + with(getter.assertNotNull("Getter")) { + type.name equals "Int" + } + } + with((this / "Bar" / "property").cast<DProperty>()) { + name equals "property" + children counts 0 + with(getter.assertNotNull("Getter")) { + type.name equals "Int" + } + } + } + } + } + + @Test + fun sinceKotlin() { + inlineModelTest( + """ + |/** + | * Quite useful [String] + | */ + |@SinceKotlin("1.1") + |val prop: String = "1.1 rulezz" + """ + ) { + with((this / "property" / "prop").cast<DProperty>()) { + with(extra[Annotations]!!.content.entries.single().value.assertNotNull("Annotations")) { + this counts 1 + with(first()) { + dri.classNames equals "SinceKotlin" + params.entries counts 1 + (params["version"].assertNotNull("version") as StringValue).value equals "\"1.1\"" + } + } + } + } + } + + @Test + fun annotatedProperty() { + inlineModelTest( + """ + |@Strictfp var property = "test" + """, + configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOfNotNull(jvmStdlibPath) + } + } + } + ) { + with((this / "property" / "property").cast<DProperty>()) { + with(extra[Annotations]!!.content.entries.single().value.assertNotNull("Annotations")) { + this counts 1 + with(first()) { + dri.classNames equals "Strictfp" + params.entries counts 0 + } + } + } + } + } + + @Test fun genericTopLevelExtensionProperty(){ + inlineModelTest( + """ | val <T : Number> List<T>.sampleProperty: T + | get() { TODO() } + """.trimIndent() + ){ + with((this / "property" / "sampleProperty").cast<DProperty>()) { + name equals "sampleProperty" + with(receiver.assertNotNull("Property receiver")) { + type.name equals "List" + } + with(getter.assertNotNull("Getter")) { + type.name equals "T" + } + setter equals null + generics counts 1 + generics.forEach { + it.name equals "T" + it.bounds.first().name equals "Number" + } + visibility.values allEquals KotlinVisibility.Public + } + } + } + + @Test fun genericExtensionPropertyInClass(){ + inlineModelTest( + """ | package test + | class XD<T> { + | var List<T>.sampleProperty: T + | get() { TODO() } + | set(value) { TODO() } + | } + """.trimIndent() + ){ + with((this / "property" / "XD" / "sampleProperty").cast<DProperty>()) { + name equals "sampleProperty" + children counts 0 + with(receiver.assertNotNull("Property receiver")) { + type.name equals "List" + } + with(getter.assertNotNull("Getter")) { + type.name equals "T" + } + with(setter.assertNotNull("Setter")){ + type.name equals "Unit" + } + generics counts 0 + visibility.values allEquals KotlinVisibility.Public + } + } + } +// @Test +// fun annotatedProperty() { +// checkSourceExistsAndVerifyModel( +// "testdata/properties/annotatedProperty.kt", +// modelConfig = ModelConfig( +// analysisPlatform = analysisPlatform, +// withKotlinRuntime = true +// ) +// ) { model -> +// with(model.members.single().members.single()) { +// Assert.assertEquals(1, annotations.count()) +// with(annotations[0]) { +// Assert.assertEquals("Strictfp", name) +// Assert.assertEquals(Content.Empty, content) +// Assert.assertEquals(NodeKind.Annotation, kind) +// } +// } +// } +// } +// +//} +} diff --git a/plugins/base/src/test/kotlin/multiplatform/BasicMultiplatformTest.kt b/plugins/base/src/test/kotlin/multiplatform/BasicMultiplatformTest.kt new file mode 100644 index 00000000..b3ac7b07 --- /dev/null +++ b/plugins/base/src/test/kotlin/multiplatform/BasicMultiplatformTest.kt @@ -0,0 +1,54 @@ +package multiplatform + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest + +class BasicMultiplatformTest : AbstractCoreTest() { + + @Test + fun dataTestExample() { + val testDataDir = getTestDataDir("multiplatform/basicMultiplatformTest").toAbsolutePath() + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("$testDataDir/jvmMain/") + } + } + } + + testFromData(configuration) { + pagesTransformationStage = { + assertEquals(6, it.children.firstOrNull()?.children?.count() ?: 0) + } + } + } + + @Test + fun inlineTestExample() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/multiplatform/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/multiplatform/Test.kt + |package multiplatform + | + |object Test { + | fun test2(str: String): Unit {println(str)} + |} + """.trimMargin(), + configuration + ) { + pagesGenerationStage = { + assertEquals(3, it.parentMap.size) + } + } + } +} diff --git a/plugins/base/src/test/kotlin/pageMerger/PageNodeMergerTest.kt b/plugins/base/src/test/kotlin/pageMerger/PageNodeMergerTest.kt new file mode 100644 index 00000000..935b9377 --- /dev/null +++ b/plugins/base/src/test/kotlin/pageMerger/PageNodeMergerTest.kt @@ -0,0 +1,126 @@ +package pageMerger + +import org.jetbrains.dokka.pages.ContentPage +import org.jetbrains.dokka.pages.PageNode +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest + +class PageNodeMergerTest : AbstractCoreTest() { + + /* object SameNameStrategy : DokkaPlugin() { + val strategy by extending { CoreExtensions.pageMergerStrategy with SameMethodNamePageMergerStrategy } + } + + class DefaultStrategy(val strList: MutableList<String> = mutableListOf()) : DokkaPlugin(), DokkaLogger { + val strategy by extending { CoreExtensions.pageMergerStrategy with DefaultPageMergerStrategy(this@DefaultStrategy) } + + override var warningsCount: Int = 0 + override var errorsCount: Int = 0 + + override fun debug(message: String) = TODO() + + override fun info(message: String) = TODO() + + override fun progress(message: String) = TODO() + + override fun warn(message: String) { + strList += message + } + + override fun error(message: String) = TODO() + + override fun report() = TODO() + } + */ + + @Test + fun sameNameStrategyTest() { + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/pageMerger/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |fun testT(): Int = 1 + |fun testT(i: Int): Int = i + | + |object Test { + | fun test(): String = "" + | fun test(str: String): String = str + |} + """.trimMargin(), + configuration/*, + pluginOverrides = listOf(SameNameStrategy)*/ + ) { + pagesTransformationStage = { + val allChildren = it.childrenRec().filterIsInstance<ContentPage>() + val testT = allChildren.filter { it.name == "testT" } + val test = allChildren.filter { it.name == "test" } + + assertTrue(testT.size == 1) { "There can be only one testT page" } + assertTrue(testT.first().dri.size == 2) { "testT page should have 2 DRI, but has ${testT.first().dri.size}" } + + assertTrue(test.size == 1) { "There can be only one test page" } + assertTrue(test.first().dri.size == 2) { "test page should have 2 DRI, but has ${test.first().dri.size}" } + } + } + } + + @Disabled("TODO: reenable when we have infrastructure for turning off extensions") + @Test + fun defaultStrategyTest() { + val strList: MutableList<String> = mutableListOf() + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/pageMerger/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/pageMerger/Test.kt + |package pageMerger + | + |fun testT(): Int = 1 + |fun testT(i: Int): Int = i + | + |object Test { + | fun test(): String = "" + | fun test(str: String): String = str + |} + """.trimMargin(), + configuration/*, + pluginOverrides = listOf(DefaultStrategy(strList)) */ + ) { + pagesTransformationStage = { root -> + val allChildren = root.childrenRec().filterIsInstance<ContentPage>() + val testT = allChildren.filter { it.name == "testT" } + val test = allChildren.filter { it.name == "test" } + + assertTrue(testT.size == 1) { "There can be only one testT page" } + assertTrue(testT.first().dri.size == 1) { "testT page should have single DRI, but has ${testT.first().dri.size}" } + + assertTrue(test.size == 1) { "There can be only one test page" } + assertTrue(test.first().dri.size == 1) { "test page should have single DRI, but has ${test.first().dri.size}" } + + assertTrue(strList.count() == 2) { "Expected 2 warnings, got ${strList.count()}" } + } + } + } + + fun PageNode.childrenRec(): List<PageNode> = listOf(this) + children.flatMap { it.childrenRec() } + +} diff --git a/plugins/base/src/test/kotlin/renderers/html/DivergentTest.kt b/plugins/base/src/test/kotlin/renderers/html/DivergentTest.kt new file mode 100644 index 00000000..8ab277f1 --- /dev/null +++ b/plugins/base/src/test/kotlin/renderers/html/DivergentTest.kt @@ -0,0 +1,328 @@ +package renderers.html + +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.SourceRootImpl +import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.pages.ContentDivergentGroup +import org.junit.jupiter.api.Test +import renderers.* +import utils.Div +import utils.Span +import utils.match + +class DivergentTest : HtmlRenderingOnlyTestBase() { + + @Test + fun simpleWrappingCase() { + val page = TestPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(js)) { + divergent { + text("a") + } + } + } + } + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div(Div("a"))))) + } + + @Test + fun noPlatformHintCase() { + val page = TestPage { + divergentGroup(ContentDivergentGroup.GroupID("test"), implicitlySourceSetHinted = false) { + instance(setOf(DRI("test", "Test")), setOf(js)) { + divergent { + text("a") + } + } + } + } + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div("a"))) + } + + @Test + fun divergentBetweenSourceSets() { + val page = TestPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(js)) { + divergent { + text("a") + } + } + instance(setOf(DRI("test", "Test")), setOf(jvm)) { + divergent { + text("b") + } + } + instance(setOf(DRI("test", "Test")), setOf(native)) { + divergent { + text("c") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div(Div("a"), Div("b"), Div("c"))))) + } + + @Test + fun divergentInOneSourceSet() { + val page = TestPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(js)) { + divergent { + text("a") + } + } + instance(setOf(DRI("test", "Test2")), setOf(js)) { + divergent { + text("b") + } + } + instance(setOf(DRI("test", "Test3")), setOf(js)) { + divergent { + text("c") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div((Div(Div("abc")))))) + } + + @Test + fun divergentInAndBetweenSourceSets() { + val page = TestPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(native)) { + divergent { + text("a") + } + } + instance(setOf(DRI("test", "Test")), setOf(js)) { + divergent { + text("b") + } + } + instance(setOf(DRI("test", "Test")), setOf(jvm)) { + divergent { + text("c") + } + } + instance(setOf(DRI("test", "Test2")), setOf(js)) { + divergent { + text("d") + } + } + instance(setOf(DRI("test", "Test3")), setOf(native)) { + divergent { + text("e") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div(Div("ae"), Div("bd"), Div("c"))))) + } + + @Test + fun divergentInAndBetweenSourceSetsWithGrouping() { + val page = TestPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(native)) { + divergent { + text("a") + } + after { + text("a+") + } + } + instance(setOf(DRI("test", "Test")), setOf(js)) { + divergent { + text("b") + } + after { + text("bd+") + } + } + instance(setOf(DRI("test", "Test")), setOf(jvm)) { + divergent { + text("c") + } + } + instance(setOf(DRI("test", "Test2")), setOf(js)) { + divergent { + text("d") + } + after { + text("bd+") + } + } + instance(setOf(DRI("test", "Test3")), setOf(native)) { + divergent { + text("e") + } + after { + text("e+") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.match( + Div(Div(Span(Div(Div("NATIVE")))), Div(Div(Div("a"))), "a+"), + Div(Div(Span(Div(Div("JS")))), Div(Div(Div("bd"))), "bd+"), + Div(Div(Span(Div(Div("JVM")))), Div(Div(Div("c")))), + Div(Div(Span(Div(Div("NATIVE")))), Div(Div(Div("e"))), "e+"), + ) + } + + @Test + fun divergentSameBefore() { + val page = TestPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(native)) { + before { + text("ab-") + } + divergent { + text("a") + } + } + instance(setOf(DRI("test", "Test2")), setOf(native)) { + before { + text("ab-") + } + divergent { + text("b") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.match( + Div( + Div( + Div("ab-"), + Span() + ), + Div(Div(Div("ab"))) + ) + ) + } + + @Test + fun divergentSameAfter() { + val page = TestPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(native)) { + divergent { + text("a") + } + after { + text("ab+") + } + } + instance(setOf(DRI("test", "Test2")), setOf(native)) { + divergent { + text("b") + } + after { + text("ab+") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.match( + Div( + Div(Div(Div("ab"))), + "ab+" + ) + ) + } + + @Test + fun divergentGroupedByBeforeAndAfter() { + val page = TestPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(native)) { + before { + text("ab-") + } + divergent { + text("a") + } + after { + text("ab+") + } + } + instance(setOf(DRI("test", "Test2")), setOf(native)) { + before { + text("ab-") + } + divergent { + text("b") + } + after { + text("ab+") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.match( + Div( + Div(Div("ab-"), Span()), + Div(Div(Div("ab"))), + "ab+" + ) + ) + } + + @Test + fun divergentDifferentBeforeAndAfter() { + val page = TestPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(native)) { + before { + text("a-") + } + divergent { + text("a") + } + after { + text("ab+") + } + } + instance(setOf(DRI("test", "Test2")), setOf(native)) { + before { + text("b-") + } + divergent { + text("b") + } + after { + text("ab+") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.match( + Div(Div(Div("a-"), Span()), Div(Div(Div("a"))), "ab+"), + Div(Div(Div("b-"), Span()), Div(Div(Div(("b")))), "ab+") + ) + } +} diff --git a/plugins/base/src/test/kotlin/renderers/html/GroupWrappingTest.kt b/plugins/base/src/test/kotlin/renderers/html/GroupWrappingTest.kt new file mode 100644 index 00000000..c0c03998 --- /dev/null +++ b/plugins/base/src/test/kotlin/renderers/html/GroupWrappingTest.kt @@ -0,0 +1,78 @@ +package renderers.html + +import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jetbrains.dokka.pages.TextStyle +import org.junit.jupiter.api.Test +import renderers.* +import utils.Div +import utils.P +import utils.match + +class GroupWrappingTest : HtmlRenderingOnlyTestBase() { + + @Test + fun notWrapped() { + val page = TestPage { + group { + text("a") + text("b") + } + text("c") + } + + HtmlRenderer(context).render(page) + + renderedContent.match("abc") + } + + @Test + fun paragraphWrapped() { + val page = TestPage { + group(styles = setOf(TextStyle.Paragraph)) { + text("a") + text("b") + } + text("c") + } + + HtmlRenderer(context).render(page) + + renderedContent.match(P("ab"), "c") + } + + @Test + fun blockWrapped() { + val page = TestPage { + group(styles = setOf(TextStyle.Block)) { + text("a") + text("b") + } + text("c") + } + + HtmlRenderer(context).render(page) + + renderedContent.match(Div("ab"), "c") + } + + @Test + fun nested() { + val page = TestPage { + group(styles = setOf(TextStyle.Block)) { + text("a") + group(styles = setOf(TextStyle.Block)) { + group(styles = setOf(TextStyle.Block)) { + text("b") + text("c") + } + } + text("d") + } + } + + HtmlRenderer(context).render(page) + + renderedContent.match(Div("a", Div(Div("bc")), "d")) + } + +} diff --git a/plugins/base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt b/plugins/base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt new file mode 100644 index 00000000..b6765fda --- /dev/null +++ b/plugins/base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt @@ -0,0 +1,90 @@ +package renderers.html + +import org.jetbrains.dokka.DokkaConfigurationImpl +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.SourceRootImpl +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.renderers.DefaultTabSortingStrategy +import org.jetbrains.dokka.base.renderers.RootCreator +import org.jetbrains.dokka.base.resolvers.external.DokkaExternalLocationProviderFactory +import org.jetbrains.dokka.base.resolvers.external.JavadocExternalLocationProviderFactory +import org.jetbrains.dokka.base.resolvers.local.DefaultLocationProviderFactory +import org.jetbrains.dokka.testApi.context.MockContext +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import org.jsoup.nodes.Node +import org.jsoup.nodes.TextNode +import renderers.RenderingOnlyTestBase +import utils.TestOutputWriter +import renderers.defaultSourceSet + +abstract class HtmlRenderingOnlyTestBase : RenderingOnlyTestBase<Element>() { + + protected val js = defaultSourceSet.copy( + "root", + "JS", + defaultSourceSet.sourceSetID.copy(sourceSetName = "js"), + analysisPlatform = Platform.js, + sourceRoots = listOf(SourceRootImpl("pl1")) + ) + protected val jvm = defaultSourceSet.copy( + "root", + "JVM", + defaultSourceSet.sourceSetID.copy(sourceSetName = "jvm"), + + analysisPlatform = Platform.jvm, + sourceRoots = listOf(SourceRootImpl("pl1")) + ) + protected val native = defaultSourceSet.copy( + "root", + "NATIVE", + defaultSourceSet.sourceSetID.copy(sourceSetName = "native"), + analysisPlatform = Platform.native, + sourceRoots = listOf(SourceRootImpl("pl1")) + ) + + val files = TestOutputWriter() + override val context = MockContext( + DokkaBase().outputWriter to { _ -> files }, + DokkaBase().locationProviderFactory to ::DefaultLocationProviderFactory, + DokkaBase().htmlPreprocessors to { _ -> RootCreator }, + DokkaBase().externalLocationProviderFactory to { ::JavadocExternalLocationProviderFactory }, + DokkaBase().externalLocationProviderFactory to { ::DokkaExternalLocationProviderFactory }, + DokkaBase().tabSortingStrategy to { DefaultTabSortingStrategy() }, + testConfiguration = DokkaConfigurationImpl( + "", null, false, listOf(js, jvm, native), emptyList(), emptyMap(), emptyList(), false + ) + ) + + override val renderedContent: Element by lazy { + files.contents.getValue("test-page.html").let { Jsoup.parse(it) }.select("#content").single() + } + + protected fun linesAfterContentTag() = + files.contents.getValue("test-page.html").lines() + .dropWhile { !it.contains("""<div id="content">""") } + .joinToString(separator = "") { it.trim() } +} + +fun Element.match(vararg matchers: Any): Unit = + childNodes() + .filter { it !is TextNode || it.text().isNotBlank() } + .let { it.drop(it.size - matchers.size) } + .zip(matchers) + .forEach { (n, m) -> m.accepts(n) } + +open class Tag(val name: String, vararg val matchers: Any) +class Div(vararg matchers: Any) : Tag("div", *matchers) +class P(vararg matchers: Any) : Tag("p", *matchers) +class Span(vararg matchers: Any) : Tag("span", *matchers) + +private fun Any.accepts(n: Node) { + when (this) { + is String -> assert(n is TextNode && n.text().trim() == this.trim()) { "\"$this\" expected but found: $n" } + is Tag -> { + assert(n is Element && n.tagName() == name) { "Tag $name expected but found: $n" } + if (n is Element && matchers.isNotEmpty()) n.match(*matchers) + } + else -> throw IllegalArgumentException("$this is not proper matcher") + } +} diff --git a/plugins/base/src/test/kotlin/renderers/html/SourceSetDependentHintTest.kt b/plugins/base/src/test/kotlin/renderers/html/SourceSetDependentHintTest.kt new file mode 100644 index 00000000..cf7f47e6 --- /dev/null +++ b/plugins/base/src/test/kotlin/renderers/html/SourceSetDependentHintTest.kt @@ -0,0 +1,139 @@ +package renderers.html + +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.SourceRootImpl +import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jetbrains.dokka.pages.TextStyle +import org.junit.jupiter.api.Test +import renderers.TestPage +import renderers.defaultSourceSet +import renderers.RenderingOnlyTestBase +import utils.Div +import utils.match + +class SourceSetDependentHintTest : HtmlRenderingOnlyTestBase() { + + private val pl1 = defaultSourceSet.copy( + "root", + "pl1", + defaultSourceSet.sourceSetID.copy(sourceSetName = "pl1"), + analysisPlatform = Platform.js, + sourceRoots = listOf(SourceRootImpl("pl1")) + ) + private val pl2 = defaultSourceSet.copy( + "root", + "pl2", + defaultSourceSet.sourceSetID.copy(sourceSetName = "pl2"), + analysisPlatform = Platform.jvm, + sourceRoots = listOf(SourceRootImpl("pl1")) + ) + private val pl3 = defaultSourceSet.copy( + "root", + "pl3", + defaultSourceSet.sourceSetID.copy(sourceSetName = "pl3"), + analysisPlatform = Platform.native, + sourceRoots = listOf(SourceRootImpl("pl1")) + ) + + @Test + fun platformIndependentCase() { + val page = TestPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2, pl3), styles = setOf(TextStyle.Block)) { + text("a") + text("b") + text("c") + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div("abc")))) + } + + @Test + fun completelyDivergentCase() { + val page = TestPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2, pl3), styles = setOf(TextStyle.Block)) { + text("a", sourceSets = setOf(pl1)) + text("b", sourceSets = setOf(pl2)) + text("c", sourceSets = setOf(pl3)) + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div("a")), Div(Div("b")), Div(Div("c")))) + } + + @Test + fun overlappingCase() { + val page = TestPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2), styles = setOf(TextStyle.Block)) { + text("a", sourceSets = setOf(pl1)) + text("b", sourceSets = setOf(pl1, pl2)) + text("c", sourceSets = setOf(pl2)) + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div("ab")), Div(Div("bc")))) + } + + @Test + fun caseThatCanBeSimplified() { + val page = TestPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2), styles = setOf(TextStyle.Block)) { + text("a", sourceSets = setOf(pl1, pl2)) + text("b", sourceSets = setOf(pl1)) + text("b", sourceSets = setOf(pl2)) + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div("ab")))) + } + + @Test + fun caseWithGroupBreakingSimplification() { + val page = TestPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2), styles = setOf(TextStyle.Block)) { + group(styles = setOf(TextStyle.Block)) { + text("a", sourceSets = setOf(pl1, pl2)) + text("b", sourceSets = setOf(pl1)) + } + text("b", sourceSets = setOf(pl2)) + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div(Div("ab"))), Div(Div(Div("a"), "b")))) + } + + @Test + fun caseWithGroupNotBreakingSimplification() { + val page = TestPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2)) { + group { + text("a", sourceSets = setOf(pl1, pl2)) + text("b", sourceSets = setOf(pl1)) + } + text("b", sourceSets = setOf(pl2)) + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div("ab"))) + } + + @Test + fun partiallyUnifiedCase() { + val page = TestPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2, pl3), styles = setOf(TextStyle.Block)) { + text("a", sourceSets = setOf(pl1)) + text("a", sourceSets = setOf(pl2)) + text("b", sourceSets = setOf(pl3)) + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div("a")), Div(Div("b")))) + } +} diff --git a/plugins/base/src/test/kotlin/resourceLinks/ResourceLinksTest.kt b/plugins/base/src/test/kotlin/resourceLinks/ResourceLinksTest.kt new file mode 100644 index 00000000..4f8a834b --- /dev/null +++ b/plugins/base/src/test/kotlin/resourceLinks/ResourceLinksTest.kt @@ -0,0 +1,71 @@ +package resourceLinks + +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.plugability.DokkaPlugin +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.jetbrains.dokka.transformers.pages.PageTransformer +import org.jsoup.Jsoup +import org.junit.jupiter.api.Test +import utils.TestOutputWriterPlugin + +class ResourceLinksTest : AbstractCoreTest() { + class TestResourcesAppenderPlugin(val resources: List<String>) : DokkaPlugin() { + class TestResourcesAppender(val resources: List<String>) : PageTransformer { + override fun invoke(input: RootPageNode) = input.transformContentPagesTree { + it.modified( + embeddedResources = it.embeddedResources + resources + ) + } + } + + val appender by extending { + plugin<DokkaBase>().htmlPreprocessors with TestResourcesAppender(resources) + } + } + @Test + fun resourceLinksTest() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/test/Test.kt") + } + } + } + val absoluteResources = listOf( + "https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css", + "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" + ) + val relativeResources = listOf( + "test/relativePath.js", + "test/relativePath.css" + ) + + val source = + """ + |/src/main/kotlin/test/Test.kt + |package example + """.trimIndent() + val writerPlugin = TestOutputWriterPlugin() + testInline( + source, + configuration, + pluginOverrides = listOf(TestResourcesAppenderPlugin(absoluteResources + relativeResources), writerPlugin) + ) { + renderingStage = { + root, context -> Jsoup + .parse(writerPlugin.writer.contents["root/example.html"]) + .head() + .select("link, script") + .let { + absoluteResources.forEach { + r -> assert(it.`is`("[href=$r], [src=$r]")) + } + relativeResources.forEach { + r -> assert(it.`is`("[href=../$r] , [src=../$r]")) + } + } + } + } + } +} diff --git a/plugins/base/src/test/kotlin/signatures/DivergentSignatureTest.kt b/plugins/base/src/test/kotlin/signatures/DivergentSignatureTest.kt new file mode 100644 index 00000000..7635ab05 --- /dev/null +++ b/plugins/base/src/test/kotlin/signatures/DivergentSignatureTest.kt @@ -0,0 +1,175 @@ +package signatures + +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import org.jsoup.select.Elements +import org.junit.jupiter.api.Test +import java.nio.file.Paths +import utils.TestOutputWriterPlugin + +class DivergentSignatureTest : AbstractCoreTest() { + + @Test + fun `group { common + jvm + js }`() { + + val testDataDir = getTestDataDir("multiplatform/basicMultiplatformTest").toAbsolutePath() + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + moduleName = "example" + displayName = "js" + name = "js" + analysisPlatform = "js" + sourceRoots = listOf("jsMain", "commonMain", "jvmAndJsSecondCommonMain").map { + Paths.get("$testDataDir/$it/kotlin").toString() + } + } + sourceSet { + moduleName = "example" + displayName = "jvm" + name = "jvm" + analysisPlatform = "jvm" + sourceRoots = listOf("jvmMain", "commonMain", "jvmAndJsSecondCommonMain").map { + Paths.get("$testDataDir/$it/kotlin").toString() + } + } + sourceSet { + moduleName = "example" + displayName = "common" + name = "common" + analysisPlatform = "common" + sourceRoots = listOf("commonMain").map { + Paths.get("$testDataDir/$it/kotlin").toString() + } + } + } + } + + val writerPlugin = TestOutputWriterPlugin() + + testFromData( + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.renderedContent("example/example/-clock/get-time.html") + + assert(content.count() == 1) + assert(content.select("[data-filterable-current=example/js example/jvm example/common]").single().brief == "") + } + } + } + + @Test + fun `group { common + jvm }, group { js }`() { + + val testDataDir = getTestDataDir("multiplatform/basicMultiplatformTest").toAbsolutePath() + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + moduleName = "example" + displayName = "js" + name = "js" + analysisPlatform = "js" + sourceRoots = listOf("jsMain", "commonMain", "jvmAndJsSecondCommonMain").map { + Paths.get("$testDataDir/$it/kotlin").toString() + } + } + sourceSet { + moduleName = "example" + displayName = "jvm" + name = "jvm" + analysisPlatform = "jvm" + sourceRoots = listOf("jvmMain", "commonMain", "jvmAndJsSecondCommonMain").map { + Paths.get("$testDataDir/$it/kotlin").toString() + } + } + sourceSet { + moduleName = "example" + displayName = "common" + name = "common" + analysisPlatform = "common" + sourceRoots = listOf("commonMain").map { + Paths.get("$testDataDir/$it/kotlin").toString() + } + } + } + } + + val writerPlugin = TestOutputWriterPlugin() + + testFromData( + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.renderedContent("example/example/-clock/get-times-in-millis.html") + assert(content.count() == 2) + assert(content.select("[data-filterable-current=example/jvm example/common]").single().brief == "Time in minis") + assert(content.select("[data-filterable-current=example/js]").single().brief == "JS implementation of getTimeInMillis js" ) + } + } + } + + @Test + fun `group { js }, group { jvm }, group { js }`() { + + val testDataDir = getTestDataDir("multiplatform/basicMultiplatformTest").toAbsolutePath() + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + moduleName = "example" + displayName = "js" + name = "js" + analysisPlatform = "js" + sourceRoots = listOf("jsMain", "commonMain", "jvmAndJsSecondCommonMain").map { + Paths.get("$testDataDir/$it/kotlin").toString() + } + } + sourceSet { + moduleName = "example" + displayName = "jvm" + name = "jvm" + analysisPlatform = "jvm" + sourceRoots = listOf("jvmMain", "commonMain", "jvmAndJsSecondCommonMain").map { + Paths.get("$testDataDir/$it/kotlin").toString() + } + } + sourceSet { + moduleName = "example" + displayName = "common" + name = "common" + analysisPlatform = "common" + sourceRoots = listOf("commonMain").map { + Paths.get("$testDataDir/$it/kotlin").toString() + } + } + } + } + + val writerPlugin = TestOutputWriterPlugin() + + testFromData( + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.renderedContent("example/example/-clock/get-year.html") + assert(content.count() == 3) + assert(content.select("[data-filterable-current=example/jvm]").single().brief == "JVM custom kdoc jvm") + assert(content.select("[data-filterable-current=example/js]").single().brief == "JS custom kdoc js") + assert(content.select("[data-filterable-current=example/common]").single().brief == "common") + } + } + } + + private fun TestOutputWriterPlugin.renderedContent(path: String) = writer.contents.getValue(path) + .let { Jsoup.parse(it) }.select("#content").single().select("div.divergent-group") + + private val Element.brief: String + get() = children().select(".brief-with-platform-tags").text() +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/signatures/SignatureTest.kt b/plugins/base/src/test/kotlin/signatures/SignatureTest.kt new file mode 100644 index 00000000..9f2ae435 --- /dev/null +++ b/plugins/base/src/test/kotlin/signatures/SignatureTest.kt @@ -0,0 +1,379 @@ +package signatures + +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.jsoup.Jsoup +import org.junit.jupiter.api.Test +import utils.* + +class SignatureTest : AbstractCoreTest() { + + fun source(signature: String) = + """ + |/src/main/kotlin/test/Test.kt + |package example + | + | $signature + """.trimIndent() + + @Test + fun `fun`() { + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/test/Test.kt") + } + } + } + + val source = source("fun simpleFun(): String = \"Celebrimbor\"") + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/example/simple-fun.html").match( + "fun ", A("simpleFun"), "(): ", A("String"), Span() + ) + } + } + } + + @Test + fun `open fun`() { + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/test/Test.kt") + } + } + } + + val source = source("open fun simpleFun(): String = \"Celebrimbor\"") + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/example/simple-fun.html").match( + "open fun ", A("simpleFun"), "(): ", A("String"), Span() + ) + } + } + } + + @Test + fun `open suspend fun`() { + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/test/Test.kt") + } + } + } + + val source = source("open suspend fun simpleFun(): String = \"Celebrimbor\"") + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/example/simple-fun.html").match( + "open suspend fun ", A("simpleFun"), "(): ", A("String"), Span() + ) + } + } + } + + @Test + fun `fun with params`() { + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/test/Test.kt") + } + } + } + + val source = source("fun simpleFun(a: Int, b: Boolean, c: Any): String = \"Celebrimbor\"") + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/example/simple-fun.html").match( + "fun ", A("simpleFun"), "(a: ", A("Int"), + ", b: ", A("Boolean"), ", c: ", A("Any"), + "): ", A("String"), Span() + ) + } + } + } + + @Test + fun `fun with function param`() { + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/test/Test.kt") + } + } + } + + val source = source("fun simpleFun(a: (Int) -> String): String = \"Celebrimbor\"") + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/example/simple-fun.html").match( + "fun ", A("simpleFun"), "(a: (", A("Int"), + ") -> ", A("String"), "): ", A("String"), Span() + ) + } + } + } + + @Test + fun `fun with generic param`() { + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/test/Test.kt") + } + } + } + + val source = source("fun <T> simpleFun(): T = \"Celebrimbor\" as T") + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/example/simple-fun.html").match( + "fun <", A("T"), " : ", A("Any"), "?> ", A("simpleFun"), "(): ", + A("T"), Span() + ) + } + } + } + + @Test + fun `fun with generic bounded param`() { + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/test/Test.kt") + } + } + } + + val source = source("fun <T : String> simpleFun(): T = \"Celebrimbor\" as T") + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/example/simple-fun.html").match( + "fun <", A("T"), " : ", A("String"), "> ", A("simpleFun"), + "(): ", A("T"), Span() + ) + } + } + } + + @Test + fun `fun with keywords, params and generic bound`() { + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/test/Test.kt") + } + } + } + + val source = source("inline suspend fun <T : String> simpleFun(a: Int, b: String): T = \"Celebrimbor\" as T") + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/example/simple-fun.html").match( + "inline suspend fun <", A("T"), " : ", A("String"), "> ", A("simpleFun"), + "(a: ", A("Int"), ", b: ", A("String"), "): ", A("T"), Span() + ) + } + } + } + + + @Test + fun `fun with annotation`() { + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/test/Test.kt") + } + } + } + + val source = """ + |/src/main/kotlin/test/Test.kt + |package example + | + | @MustBeDocumented() + | @Target(AnnotationTarget.FUNCTION) + | annotation class Marking + | + | @Marking() + | fun simpleFun(): String = "Celebrimbor" + """.trimIndent() + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/example/simple-fun.html").match( + Div( + Div("@", A("Marking"), "()") + ), + "fun ", A("simpleFun"), + "(): ", A("String"), Span() + ) + } + } + } + + @Test + fun `fun with two annotations`() { + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/test/Test.kt") + } + } + } + + val source = """ + |/src/main/kotlin/test/Test.kt + |package example + | + | @MustBeDocumented() + | @Target(AnnotationTarget.FUNCTION) + | annotation class Marking(val msg: String) + | + | @MustBeDocumented() + | @Target(AnnotationTarget.FUNCTION) + | annotation class Marking2(val int: Int) + | + | @Marking("Nenya") + | @Marking2(1) + | fun simpleFun(): String = "Celebrimbor" + """.trimIndent() + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/example/simple-fun.html") + .match( + Div( + Div("@", A("Marking"), "(", Span("msg = ", Span("\"Nenya\"")), Wbr, ")"), + Div("@", A("Marking2"), "(", Span("int = ", Span("1")), Wbr, ")") + ), + "fun ", A("simpleFun"), + "(): ", A("String"), Span() + ) + } + } + } + + @Test + fun `fun with annotation with array`() { + + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/test/Test.kt") + } + } + } + + val source = """ + |/src/main/kotlin/test/Test.kt + |package example + | + | @MustBeDocumented() + | @Target(AnnotationTarget.FUNCTION) + | annotation class Marking(val msg: Array<String>) + | + | @Marking(["Nenya", "Vilya", "Narya"]) + | @Marking2(1) + | fun simpleFun(): String = "Celebrimbor" + """.trimIndent() + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/example/simple-fun.html").match( + Div( + Div("@", A("Marking"), "(", Span("msg = [", + Span(Span("\"Nenya\""), ", "), Wbr, + Span(Span("\"Vilya\""), ", "), Wbr, + Span(Span("\"Narya\"")), Wbr, "]"), Wbr, ")" + ) + ), + "fun ", A("simpleFun"), + "(): ", A("String"), Span() + ) + } + } + } + + private fun TestOutputWriter.renderedContent(path: String = "root/example.html") = + contents.getValue(path).let { Jsoup.parse(it) }.select("#content") + .single().select("div.symbol div.monospace").first() +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/transformerBuilders/PageTransformerBuilderTest.kt b/plugins/base/src/test/kotlin/transformerBuilders/PageTransformerBuilderTest.kt new file mode 100644 index 00000000..d8e057da --- /dev/null +++ b/plugins/base/src/test/kotlin/transformerBuilders/PageTransformerBuilderTest.kt @@ -0,0 +1,150 @@ +package transformerBuilders; + +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.pages.PageNode +import org.jetbrains.dokka.pages.RendererSpecificResourcePage +import org.jetbrains.dokka.pages.RenderingStrategy +import org.jetbrains.dokka.plugability.DokkaPlugin +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.jetbrains.dokka.transformers.pages.PageTransformer +import org.jetbrains.dokka.transformers.pages.pageMapper +import org.jetbrains.dokka.transformers.pages.pageScanner +import org.jetbrains.dokka.transformers.pages.pageStructureTransformer +import org.junit.jupiter.api.Test + +class PageTransformerBuilderTest : AbstractCoreTest() { + + class ProxyPlugin(transformer: PageTransformer) : DokkaPlugin() { + val pageTransformer by extending { CoreExtensions.pageTransformer with transformer } + } + + @Test + fun scannerTest() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/transformerBuilder/Test.kt") + } + } + } + val list = mutableListOf<String>() + + var orig: PageNode? = null + + testInline( + """ + |/src/main/kotlin/transformerBuilder/Test.kt + |package transformerBuilder + | + |object Test { + | fun test2(str: String): Unit {println(str)} + |} + """.trimMargin(), + configuration, + pluginOverrides = listOf(ProxyPlugin(pageScanner { + list += name + })) + ) { + pagesGenerationStage = { + orig = it + } + pagesTransformationStage = { root -> + list.assertCount(4, "Page list: ") + orig?.let { root.assertTransform(it) } + } + } + } + + @Test + fun mapperTest() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/transformerBuilder/Test.kt") + } + } + } + + var orig: PageNode? = null + + testInline( + """ + |/src/main/kotlin/transformerBuilder/Test.kt + |package transformerBuilder + | + |object Test { + | fun test2(str: String): Unit {println(str)} + |} + """.trimMargin(), + configuration, + pluginOverrides = listOf(ProxyPlugin(pageMapper { + modified(name = name + "2") + })) + ) { + pagesGenerationStage = { + orig = it + } + pagesTransformationStage = { + it.let { root -> + root.name.assertEqual("root2", "Root name: ") + orig?.let { + root.assertTransform(it) { node -> node.modified(name = node.name + "2") } + } + } + } + } + } + + @Test + fun structureTransformerTest() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin/transformerBuilder/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/transformerBuilder/Test.kt + |package transformerBuilder + | + |object Test { + | fun test2(str: String): Unit {println(str)} + |} + """.trimMargin(), + configuration, + pluginOverrides = listOf(ProxyPlugin(pageStructureTransformer { + val ch = children.first() + modified( + children = listOf( + ch, + RendererSpecificResourcePage("test", emptyList(), RenderingStrategy.DoNothing) + ) + ) + })) + ) { + pagesTransformationStage = { root -> + root.children.assertCount(2, "Root children: ") + root.children.first().name.assertEqual("transformerBuilder") + root.children[1].name.assertEqual("test") + } + } + } + + private fun <T> Collection<T>.assertCount(n: Int, prefix: String = "") = + assert(count() == n) { "${prefix}Expected $n, got ${count()}" } + + private fun <T> T.assertEqual(expected: T, prefix: String = "") = assert(this == expected) { + "${prefix}Expected $expected, got $this" + } + + private fun PageNode.assertTransform(expected: PageNode, block: (PageNode) -> PageNode = { it }): Unit = this.let { + it.name.assertEqual(block(expected).name) + it.children.zip(expected.children).forEach { (g, e) -> + g.name.assertEqual(block(e).name) + g.assertTransform(e, block) + } + } +} diff --git a/plugins/base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt b/plugins/base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt new file mode 100644 index 00000000..5197afc6 --- /dev/null +++ b/plugins/base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt @@ -0,0 +1,403 @@ +package transformers + +import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.doc.* +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import matchers.content.* +import org.jetbrains.dokka.pages.* +import org.jetbrains.kotlin.utils.addToStdlib.assertedCast + +class CommentsToContentConverterTest { + private val converter = DocTagToContentConverter + + private fun executeTest( + docTag:DocTag, + match: ContentMatcherBuilder<ContentComposite>.() -> Unit + ) { + val dci = DCI( + setOf( + DRI("kotlin", "Any") + ), + ContentKind.Comment + ) + converter.buildContent( + Li( + listOf( + docTag + ) + ), + dci, + emptySet() + ).single().assertNode(match) + } + + @Test + fun `simple text`() { + val docTag = P(listOf(Text("This is simple test of string Next line"))) + executeTest(docTag) { + +"This is simple test of string Next line" + } + } + + @Test + fun `simple text with new line`() { + val docTag = P( + listOf( + Text("This is simple test of string"), + Br, + Text("Next line") + ) + ) + executeTest(docTag) { + +"This is simple test of string" + node<ContentBreakLine>() + +"Next line" + } + } + + @Test + fun `paragraphs`() { + val docTag = P( + listOf( + P(listOf(Text("Paragraph number one"))), + P(listOf(Text("Paragraph"), Br, Text("number two"))) + ) + ) + executeTest(docTag) { + +"Paragraph number one" + +"Paragraph" + node<ContentBreakLine>() + +"number two" + } + } + + @Test + fun `unordered list with empty lines`() { + val docTag = Ul( + listOf( + Li(listOf(P(listOf(Text("list item 1 continue 1"))))), + Li(listOf(P(listOf(Text("list item 2"), Br, Text("continue 2"))))) + ) + ) + executeTest(docTag) { + node<ContentList> { + group { + +"list item 1 continue 1" + } + group { + +"list item 2" + node<ContentBreakLine>() + +"continue 2" + } + } + } + } + + @Test + fun `nested list`() { + val docTag = P( + listOf( + Ul( + listOf( + Li(listOf(P(listOf(Text("Outer first Outer next line"))))), + Li(listOf(P(listOf(Text("Outer second"))))), + Ul( + listOf( + Li(listOf(P(listOf(Text("Middle first Middle next line"))))), + Li(listOf(P(listOf(Text("Middle second"))))), + Ul( + listOf( + Li(listOf(P(listOf(Text("Inner first Inner next line"))))) + ) + ), + Li(listOf(P(listOf(Text("Middle third"))))) + ) + ), + Li(listOf(P(listOf(Text("Outer third"))))) + ) + ), + P(listOf(Text("New paragraph"))) + ) + ) + executeTest(docTag) { + node<ContentList> { + group { +"Outer first Outer next line" } + group { +"Outer second" } + node<ContentList> { + group { +"Middle first Middle next line" } + group { +"Middle second" } + node<ContentList> { + group { +"Inner first Inner next line" } + } + group { +"Middle third" } + } + group { +"Outer third" } + } + +"New paragraph" + } + } + + @Test + fun `header and paragraphs`() { + val docTag = P( + listOf( + H1(listOf(Text("Header 1"))), + P(listOf(Text("Following text"))), + P(listOf(Text("New paragraph"))) + ) + ) + executeTest(docTag) { + header(1) { +"Header 1" } + +"Following text" + +"New paragraph" + } + } + + @Test + fun `header levels`() { + val docTag = P( + listOf( + H1(listOf(Text("Header 1"))), + P(listOf(Text("Text 1"))), + H2(listOf(Text("Header 2"))), + P(listOf(Text("Text 2"))), + H3(listOf(Text("Header 3"))), + P(listOf(Text("Text 3"))), + H4(listOf(Text("Header 4"))), + P(listOf(Text("Text 4"))), + H5(listOf(Text("Header 5"))), + P(listOf(Text("Text 5"))), + H6(listOf(Text("Header 6"))), + P(listOf(Text("Text 6"))) + ) + ) + executeTest(docTag) { + header(1) {+"Header 1"} + +"Text 1" + header(2) {+"Header 2"} + +"Text 2" + header(3) {+"Header 3"} + +"Text 3" + header(4) {+"Header 4"} + +"Text 4" + header(5) {+"Header 5"} + +"Text 5" + header(6) {+"Header 6"} + +"Text 6" + } + } + + @Test + fun `block quotes`() { + val docTag = P( + listOf( + BlockQuote( + listOf( + P( + listOf( + Text("Blockquotes are very handy in email to emulate reply text. This line is part of the same quote.") + ) + ) + ) + ), + P(listOf(Text("Quote break."))), + BlockQuote( + listOf( + P(listOf(Text("Quote"))) + ) + ) + ) + ) + executeTest(docTag) { + node<ContentCodeBlock> { + +"Blockquotes are very handy in email to emulate reply text. This line is part of the same quote." + } + +"Quote break." + node<ContentCodeBlock> { + +"Quote" + } + } + } + + @Test + fun `nested block quotes`() { + val docTag = P( + listOf( + BlockQuote( + listOf( + P(listOf(Text("text 1 text 2"))), + BlockQuote( + listOf( + P(listOf(Text("text 3 text 4"))) + ) + ), + P(listOf(Text("text 5"))) + ) + ), + P(listOf(Text("Quote break."))), + BlockQuote( + listOf( + P(listOf(Text("Quote"))) + ) + ) + ) + ) + executeTest(docTag) { + node<ContentCodeBlock> { + +"text 1 text 2" + node<ContentCodeBlock> { + +"text 3 text 4" + } + +"text 5" + } + +"Quote break." + node<ContentCodeBlock> { + +"Quote" + } + } + } + + @Test + fun `multiline code`() { + val docTag = P( + listOf( + CodeBlock( + listOf( + Text("val x: Int = 0"), Br, + Text("val y: String = \"Text\""), Br, Br, + Text(" val z: Boolean = true"), Br, + Text("for(i in 0..10) {"), Br, + Text(" println(i)"), Br, + Text("}") + ), + mapOf("lang" to "kotlin") + ), + P(listOf(Text("Sample text"))) + ) + ) + executeTest(docTag) { + node<ContentCodeBlock> { + +"val x: Int = 0" + node<ContentBreakLine>() + +"val y: String = \"Text\"" + node<ContentBreakLine>() + node<ContentBreakLine>() + +" val z: Boolean = true" + node<ContentBreakLine>() + +"for(i in 0..10) {" + node<ContentBreakLine>() + +" println(i)" + node<ContentBreakLine>() + +"}" + } + +"Sample text" + } + } + + @Test + fun `inline link`() { + val docTag = P( + listOf( + A( + listOf(Text("I'm an inline-style link")), + mapOf("href" to "https://www.google.com") + ) + ) + ) + executeTest(docTag) { + link { + +"I'm an inline-style link" + check { + assertEquals( + assertedCast<ContentResolvedLink> { "Link should be resolved" }.address, + "https://www.google.com" + ) + } + } + } + } + + + + @Test + fun `ordered list`() { + val docTag = + Ol( + listOf( + Li( + listOf( + P(listOf(Text("test1"))), + P(listOf(Text("test2"))), + ) + ), + Li( + listOf( + P(listOf(Text("test3"))), + P(listOf(Text("test4"))), + ) + ) + ) + ) + executeTest(docTag) { + node<ContentList> { + group { + +"test1" + +"test2" + } + group { + +"test3" + +"test4" + } + } + } + } + + @Test + fun `nested ordered list`() { + val docTag = P( + listOf( + Ol( + listOf( + Li(listOf(P(listOf(Text("Outer first Outer next line"))))), + Li(listOf(P(listOf(Text("Outer second"))))), + Ol( + listOf( + Li(listOf(P(listOf(Text("Middle first Middle next line"))))), + Li(listOf(P(listOf(Text("Middle second"))))), + Ol( + listOf( + Li(listOf(P(listOf(Text("Inner first Inner next line"))))) + ), + mapOf("start" to "1") + ), + Li(listOf(P(listOf(Text("Middle third"))))) + ), + mapOf("start" to "1") + ), + Li(listOf(P(listOf(Text("Outer third"))))) + ), + mapOf("start" to "1") + ), + P(listOf(Text("New paragraph"))) + ) + ) + executeTest(docTag) { + node<ContentList> { + group { +"Outer first Outer next line" } + group { +"Outer second" } + node<ContentList> { + group { +"Middle first Middle next line" } + group { +"Middle second" } + node<ContentList> { + +"Inner first Inner next line" + } + group { +"Middle third" } + } + group { +"Outer third" } + } + +"New paragraph" + } + } +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/transformers/ReportUndocumentedTransformerTest.kt b/plugins/base/src/test/kotlin/transformers/ReportUndocumentedTransformerTest.kt new file mode 100644 index 00000000..72948372 --- /dev/null +++ b/plugins/base/src/test/kotlin/transformers/ReportUndocumentedTransformerTest.kt @@ -0,0 +1,919 @@ +package transformers + +import org.jetbrains.dokka.PackageOptionsImpl +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test + + +class ReportUndocumentedTransformerTest : AbstractCoreTest() { + @Test + fun `undocumented class gets reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/kotlin/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + | + |class X + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport(Regex("init")) + assertSingleUndocumentedReport(Regex("""sample/X/""")) + } + } + } + + @Test + fun `undocumented non-public class does not get reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/kotlin/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + | + |internal class X + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport() + } + } + } + + @Test + fun `undocumented function gets reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/kotlin/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + | + |/** Documented */ + |class X { + | fun x() + |} + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertSingleUndocumentedReport(Regex("X")) + assertSingleUndocumentedReport(Regex("X/x")) + } + } + } + + @Test + fun `undocumented property gets reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/kotlin/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + | + |/** Documented */ + |class X { + | val x: Int = 0 + |} + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertSingleUndocumentedReport(Regex("X")) + assertSingleUndocumentedReport(Regex("X/x")) + } + } + } + + @Test + fun `undocumented primary constructor does not get reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/kotlin/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + | + |/** Documented */ + |class X(private val x: Int) { + |} + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport() + } + } + } + + @Test + fun `data class component functions do not get reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/kotlin") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + | + |/** Documented */ + |data class X(val x: Int) { + |} + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport(Regex("component")) + assertNumberOfUndocumentedReports(1) + } + } + } + + @Disabled + @Test + fun `undocumented secondary constructor gets reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/kotlin/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + | + |/** Documented */ + |class X { + | constructor(unit: Unit) : this() + |} + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertSingleUndocumentedReport(Regex("X")) + assertSingleUndocumentedReport(Regex("X.*init.*Unit")) + } + } + } + + @Test + fun `undocumented inherited function does not get reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/kotlin/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + | + |/** Documented */ + |open class A { + | fun a() = Unit + |} + | + |/** Documented */ + |class B : A() + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport(Regex("B")) + assertSingleUndocumentedReport(Regex("A.*a")) + } + } + } + + @Test + fun `undocumented inherited property does not get reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/kotlin/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + | + |/** Documented */ + |open class A { + | val a = Unit + |} + | + |/** Documented */ + |class B : A() + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport(Regex("B")) + assertSingleUndocumentedReport(Regex("A.*a")) + } + } + } + + @Test + fun `overridden function does not get reported when super is documented`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/kotlin/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + |import kotlin.Exception + | + |/** Documented */ + |open class A { + | /** Documented */ + | fun a() = Unit + |} + | + |/** Documented */ + |class B : A() { + | override fun a() = throw Exception() + |} + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport() + } + } + } + + @Test + fun `overridden property does not get reported when super is documented`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/kotlin/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + |import kotlin.Exception + | + |/** Documented */ + |open class A { + | /** Documented */ + | open val a = 0 + |} + | + |/** Documented */ + |class B : A() { + | override val a = 1 + |} + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport() + } + } + } + + @Test + fun `report disabled by source set`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = false + sourceRoots = listOf("src/main/kotlin/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + | + |class X + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport() + } + } + } + + @Test + fun `report enabled by package configuration`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + perPackageOptions += packageOptions( + prefix = "sample", + reportUndocumented = true, + ) + reportUndocumented = false + sourceRoots = listOf("src/main/kotlin/Test.kt") + } + } + } + + testInline( + """ + |/src/main/kotlin/Test.kt + |package sample + | + |class X + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertSingleUndocumentedReport(Regex("X")) + } + } + } + + @Test + fun `report enabled by more specific package configuration`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + perPackageOptions += packageOptions( + prefix = "sample", + reportUndocumented = false, + ) + perPackageOptions += packageOptions( + prefix = "sample.enabled", + reportUndocumented = true, + ) + reportUndocumented = false + sourceRoots = listOf("src/main/kotlin/") + } + } + } + + testInline( + """ + |/src/main/kotlin/sample/disabled/Disabled.kt + |package sample.disabled + |class Disabled + | + |/src/main/kotlin/sample/enabled/Enabled.kt + |package sample.enabled + |class Enabled + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertSingleUndocumentedReport(Regex("Enabled")) + assertNumberOfUndocumentedReports(1) + } + } + } + + @Test + fun `report disabled by more specific package configuration`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + perPackageOptions += packageOptions( + prefix = "sample", + reportUndocumented = true, + ) + perPackageOptions += packageOptions( + prefix = "sample.disabled", + reportUndocumented = false, + ) + reportUndocumented = true + sourceRoots = listOf("src/main/kotlin/") + } + } + } + + testInline( + """ + |/src/main/kotlin/sample/disabled/Disabled.kt + |package sample.disabled + |class Disabled + | + |/src/main/kotlin/sample/enabled/Enabled.kt + |package sample.enabled + |class Enabled + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertSingleUndocumentedReport(Regex("Enabled")) + assertNumberOfUndocumentedReports(1) + } + } + } + + @Test + fun `multiplatform undocumented class gets reported`() { + val configuration = dokkaConfiguration { + sourceSets { + val commonMain = sourceSet { + reportUndocumented = true + analysisPlatform = Platform.common.toString() + name = "commonMain" + displayName = "commonMain" + sourceRoots = listOf("src/commonMain/kotlin") + } + + sourceSet { + reportUndocumented = true + analysisPlatform = Platform.jvm.toString() + name = "jvmMain" + displayName = "jvmMain" + sourceRoots = listOf("src/jvmMain/kotlin") + dependentSourceSets = setOf(commonMain.sourceSetID) + } + } + } + + testInline( + """ + |/src/commonMain/kotlin/sample/Common.kt + |package sample + |expect class X + | + |/src/jvmMain/kotlin/sample/JvmMain.kt + |package sample + |actual class X + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNumberOfUndocumentedReports(2, Regex("X")) + assertSingleUndocumentedReport(Regex("X.*jvmMain")) + assertSingleUndocumentedReport(Regex("X.*commonMain")) + } + } + } + + @Test + fun `multiplatform undocumented class does not get reported if expect is documented`() { + val configuration = dokkaConfiguration { + sourceSets { + val commonMain = sourceSet { + reportUndocumented = true + analysisPlatform = Platform.common.toString() + name = "commonMain" + displayName = "commonMain" + sourceRoots = listOf("src/commonMain/kotlin") + } + + sourceSet { + reportUndocumented = true + analysisPlatform = Platform.jvm.toString() + name = "jvmMain" + displayName = "jvmMain" + sourceRoots = listOf("src/jvmMain/kotlin") + dependentSourceSets = setOf(commonMain.sourceSetID) + } + } + } + + testInline( + """ + |/src/commonMain/kotlin/sample/Common.kt + |package sample + |/** Documented */ + |expect class X + | + |/src/jvmMain/kotlin/sample/JvmMain.kt + |package sample + |actual class X + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNumberOfUndocumentedReports(0) + } + } + } + + @Test + fun `multiplatform undocumented function gets reported`() { + val configuration = dokkaConfiguration { + sourceSets { + val commonMain = sourceSet { + reportUndocumented = true + analysisPlatform = Platform.common.toString() + name = "commonMain" + displayName = "commonMain" + sourceRoots = listOf("src/commonMain/kotlin") + } + + sourceSet { + reportUndocumented = true + analysisPlatform = Platform.jvm.toString() + name = "jvmMain" + displayName = "jvmMain" + sourceRoots = listOf("src/jvmMain/kotlin") + dependentSourceSets = setOf(commonMain.sourceSetID) + } + + sourceSet { + reportUndocumented = true + analysisPlatform = Platform.native.toString() + name = "macosMain" + displayName = "macosMain" + sourceRoots = listOf("src/macosMain/kotlin") + dependentSourceSets = setOf(commonMain.sourceSetID) + } + } + } + + testInline( + """ + |/src/commonMain/kotlin/sample/Common.kt + |package sample + |expect fun x() + | + |/src/macosMain/kotlin/sample/MacosMain.kt + |package sample + |/** Documented */ + |actual fun x() = Unit + | + |/src/jvmMain/kotlin/sample/JvmMain.kt + |package sample + |actual fun x() = Unit + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNumberOfUndocumentedReports(2) + assertSingleUndocumentedReport(Regex("x.*commonMain")) + assertSingleUndocumentedReport(Regex("x.*jvmMain")) + } + } + } + + @Test + fun `java undocumented class gets reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/java") + } + } + } + + testInline( + """ + |/src/main/java/sample/Test.java + |package sample + |public class Test { } + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport(Regex("init")) + assertSingleUndocumentedReport(Regex("""Test""")) + assertNumberOfUndocumentedReports(1) + } + } + } + + @Test + fun `java undocumented non-public class does not get reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/java") + } + } + } + + testInline( + """ + |/src/main/java/sample/Test.java + |package sample + |class Test { } + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport() + } + } + } + + @Test + fun `java undocumented constructor does not get reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/java") + } + } + } + + testInline( + """ + |/src/main/java/sample/Test.java + |package sample + |/** Documented */ + |public class Test { + | public Test() { + | } + |} + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport() + } + } + } + + @Test + fun `java undocumented method gets reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/java") + } + } + } + + testInline( + """ + |/src/main/java/sample/X.java + |package sample + |/** Documented */ + |public class X { + | public void x { } + |} + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertSingleUndocumentedReport(Regex("X")) + assertSingleUndocumentedReport(Regex("X.*x")) + assertNumberOfUndocumentedReports(1) + } + } + } + + @Test + fun `java undocumented property gets reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/java") + } + } + } + + testInline( + """ + |/src/main/java/sample/X.java + |package sample + |/** Documented */ + |public class X { + | public int x = 0; + |} + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertSingleUndocumentedReport(Regex("X")) + assertSingleUndocumentedReport(Regex("X.*x")) + assertNumberOfUndocumentedReports(1) + } + } + } + + @Test + fun `java undocumented inherited method gets reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/java") + } + } + } + + testInline( + """ + |/src/main/java/sample/Super.java + |package sample + |/** Documented */ + |public class Super { + | public void x() {} + |} + | + |/src/main/java/sample/X.java + |package sample + |/** Documented */ + |public class X extends Super { + | public void x() {} + |} + | + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertSingleUndocumentedReport(Regex("X")) + assertSingleUndocumentedReport(Regex("X.*x")) + assertSingleUndocumentedReport(Regex("Super.*x")) + assertNumberOfUndocumentedReports(2) + } + } + } + + @Test + fun `java documented inherited method does not get reported`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/java") + } + } + } + + testInline( + """ + |/src/main/java/sample/Super.java + |package sample + |/** Documented */ + |public class Super { + | /** Documented */ + | public void x() {} + |} + | + |/src/main/java/sample/X.java + |package sample + |/** Documented */ + |public class X extends Super { + | + |} + | + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport() + } + } + } + + @Test + fun `java overridden function does not get reported when super is documented`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + reportUndocumented = true + sourceRoots = listOf("src/main/java") + } + } + } + + testInline( + """ + |/src/main/java/sample/Super.java + |package sample + |/** Documented */ + |public class Super { + | /** Documented */ + | public void x() {} + |} + | + |/src/main/java/sample/X.java + |package sample + |/** Documented */ + |public class X extends Super { + | @Override + | public void x() {} + |} + | + """.trimMargin(), + configuration + ) { + pagesTransformationStage = { + assertNoUndocumentedReport() + } + } + } + + private fun assertNumberOfUndocumentedReports(expectedReports: Int, regex: Regex = Regex(".")) { + val reports = logger.warnMessages + .filter { it.startsWith("Undocumented:") } + val matchingReports = reports + .filter { it.contains(regex) } + + assertEquals( + expectedReports, matchingReports.size, + "Expected $expectedReports report of documented code ($regex).\n" + + "Found matching reports: $matchingReports\n" + + "Found reports: $reports" + ) + } + + private fun assertSingleUndocumentedReport(regex: Regex) { + assertNumberOfUndocumentedReports(1, regex) + } + + private fun assertNoUndocumentedReport(regex: Regex) { + assertNumberOfUndocumentedReports(0, regex) + } + + private fun assertNoUndocumentedReport() { + assertNoUndocumentedReport(Regex(".")) + } + + private fun packageOptions( + prefix: String, + reportUndocumented: Boolean?, + includeNonPublic: Boolean = true, + skipDeprecated: Boolean = false, + suppress: Boolean = false + ) = PackageOptionsImpl( + prefix = prefix, + reportUndocumented = reportUndocumented, + includeNonPublic = includeNonPublic, + skipDeprecated = skipDeprecated, + suppress = suppress + ) +} diff --git a/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt b/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt new file mode 100644 index 00000000..b0754429 --- /dev/null +++ b/plugins/base/src/test/kotlin/translators/DefaultDescriptorToDocumentableTranslatorTest.kt @@ -0,0 +1,87 @@ +package translators + +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class DefaultDescriptorToDocumentableTranslatorTest : AbstractCoreTest() { + + @Test + fun `data class kdocs over generated methods`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin") + } + } + } + + testInline( + """ + |/src/main/kotlin/sample/XD.kt + |package sample + |/** + | * But the fat Hobbit, he knows. Eyes always watching. + | */ + |data class XD(val xd: String) { + | /** + | * But the fat Hobbit, he knows. Eyes always watching. + | */ + | fun custom(): String = "" + | + | /** + | * Memory is not what the heart desires. That is only a mirror. + | */ + | override fun equals(other: Any?): Boolean = true + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + assert(module.documentationOf("XD", "copy") == "") + assert(module.documentationOf("XD", "equals") == "Memory is not what the heart desires. That is only a mirror.") + assert(module.documentationOf("XD", "hashCode") == "") + assert(module.documentationOf("XD", "toString") == "") + assert(module.documentationOf("XD", "custom") == "But the fat Hobbit, he knows. Eyes always watching.") + } + } + } + + @Test + fun `simple class kdocs`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/kotlin") + } + } + } + + testInline( + """ + |/src/main/kotlin/sample/XD.kt + |package sample + |/** + | * But the fat Hobbit, he knows. Eyes always watching. + | */ + |class XD(val xd: String) { + | /** + | * But the fat Hobbit, he knows. Eyes always watching. + | */ + | fun custom(): String = "" + | + | /** + | * Memory is not what the heart desires. That is only a mirror. + | */ + | override fun equals(other: Any?): Boolean = true + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + assert(module.documentationOf("XD", "custom") == "But the fat Hobbit, he knows. Eyes always watching.") + assert(module.documentationOf("XD", "equals") == "Memory is not what the heart desires. That is only a mirror.") + } + } + } +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt b/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt new file mode 100644 index 00000000..eb682b14 --- /dev/null +++ b/plugins/base/src/test/kotlin/translators/DefaultPsiToDocumentableTranslatorTest.kt @@ -0,0 +1,144 @@ +package translators + +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.model.doc.Description +import org.jetbrains.dokka.model.doc.Text +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class DefaultPsiToDocumentableTranslatorTest : AbstractCoreTest() { + + @Test + fun `method overriding two documented classes picks closest class documentation`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/java") + } + } + } + + testInline( + """ + |/src/main/java/sample/BaseClass1.java + |package sample + |public class BaseClass1 { + | /** B1 */ + | void x() { } + |} + | + |/src/main/java/sample/BaseClass2.java + |package sample + |public class BaseClass2 extends BaseClass1 { + | /** B2 */ + | void x() { } + |} + | + |/src/main/java/sample/X.java + |package sample + |public class X extends BaseClass2 { + | void x() { } + |} + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + val documentationOfFunctionX = module.documentationOf("X", "x") + assertTrue( + "B2" in documentationOfFunctionX, + "Expected nearest super method documentation to be parsed as documentation. " + + "Documentation: $documentationOfFunctionX" + ) + } + } + } + + @Test + fun `method overriding class and interface picks class documentation`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/java") + } + } + } + + testInline( + """ + |/src/main/java/sample/BaseClass1.java + |package sample + |public class BaseClass1 { + | /** B1 */ + | void x() { } + |} + | + |/src/main/java/sample/Interface1.java + |package sample + |public interface Interface1 { + | /** I1 */ + | void x() {} + |} + | + |/src/main/java/sample/X.java + |package sample + |public class X extends BaseClass1 implements Interface1 { + | void x() { } + |} + """.trimMargin(), + configuration + ) { + documentablesMergingStage = { module -> + val documentationOfFunctionX = module.documentationOf("X", "x") + assertTrue( + "B1" in documentationOfFunctionX, + "Expected documentation of superclass being prioritized over interface " + + "Documentation: $documentationOfFunctionX" + ) + } + } + } + + @Test + fun `method overriding two classes picks closest documented class documentation`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/main/java") + } + } + } + + testInline( + """ + |/src/main/java/sample/BaseClass1.java + |package sample + |public class BaseClass1 { + | /** B1 */ + | void x() { } + |} + | + |/src/main/java/sample/BaseClass2.java + |package sample + |public class BaseClass2 extends BaseClass1 { + | void x() {} + |} + | + |/src/main/java/sample/X.java + |package sample + |public class X extends BaseClass2 { + | void x() { } + |} + """.trimMargin(), + configuration + ) { + documentablesMergingStage = { module -> + val documentationOfFunctionX = module.documentationOf("X", "x") + assertTrue( + "B1" in documentationOfFunctionX, + "Expected Documentation \"B1\", found: \"$documentationOfFunctionX\"" + ) + } + } + } +} diff --git a/plugins/base/src/test/kotlin/translators/utils.kt b/plugins/base/src/test/kotlin/translators/utils.kt new file mode 100644 index 00000000..96d3035a --- /dev/null +++ b/plugins/base/src/test/kotlin/translators/utils.kt @@ -0,0 +1,16 @@ +package translators + +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.model.doc.Description +import org.jetbrains.dokka.model.doc.Text + +fun DModule.documentationOf(className: String, functionName: String): String { + return (packages.single() + .classlikes.single { it.name == className } + .functions.single { it.name == functionName } + .documentation.values.singleOrNull() + ?.children?.singleOrNull() + .run { this as? Description } + ?.root?.children?.single() as? Text) + ?.body.orEmpty() +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/utils/JsoupUtils.kt b/plugins/base/src/test/kotlin/utils/JsoupUtils.kt new file mode 100644 index 00000000..e8c7838d --- /dev/null +++ b/plugins/base/src/test/kotlin/utils/JsoupUtils.kt @@ -0,0 +1,29 @@ +package utils + +import org.jsoup.nodes.Element +import org.jsoup.nodes.Node +import org.jsoup.nodes.TextNode + +fun Element.match(vararg matchers: Any): Unit = + childNodes() + .filter { it !is TextNode || it.text().isNotBlank() } + .let { it.drop(it.size - matchers.size) } + .zip(matchers) + .forEach { (n, m) -> m.accepts(n) } + +open class Tag(val name: String, vararg val matchers: Any) +class Div(vararg matchers: Any) : Tag("div", *matchers) +class P(vararg matchers: Any) : Tag("p", *matchers) +class Span(vararg matchers: Any) : Tag("span", *matchers) +class A(vararg matchers: Any) : Tag("a", *matchers) +object Wbr : Tag("wbr") +private fun Any.accepts(n: Node) { + when (this) { + is String -> assert(n is TextNode && n.text().trim() == this.trim()) { "\"$this\" expected but found: $n" } + is Tag -> { + assert(n is Element && n.tagName() == name) { "Tag $name expected but found: $n" } + if (n is Element && matchers.isNotEmpty()) n.match(*matchers) + } + else -> throw IllegalArgumentException("$this is not proper matcher") + } +}
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/utils/ModelUtils.kt b/plugins/base/src/test/kotlin/utils/ModelUtils.kt new file mode 100644 index 00000000..87a9c802 --- /dev/null +++ b/plugins/base/src/test/kotlin/utils/ModelUtils.kt @@ -0,0 +1,37 @@ +package utils + +import org.jetbrains.dokka.DokkaConfigurationImpl +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.plugability.DokkaPlugin + +abstract class AbstractModelTest(val path: String? = null, val pkg: String) : ModelDSL(), AssertDSL { + + fun inlineModelTest( + query: String, + platform: String = "jvm", + prependPackage: Boolean = true, + cleanupOutput: Boolean = true, + pluginsOverrides: List<DokkaPlugin> = emptyList(), + configuration: DokkaConfigurationImpl? = null, + block: DModule.() -> Unit + ) { + val testConfiguration = configuration ?: dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + analysisPlatform = platform + } + } + } + val prepend = path.let { p -> p?.let { "|$it\n" } ?: "" } + if (prependPackage) "|package $pkg" else "" + + testInline( + query = ("$prepend\n$query").trim().trimIndent(), + configuration = testConfiguration, + cleanupOutput = cleanupOutput, + pluginOverrides = pluginsOverrides + ) { + documentablesTransformationStage = block + } + } +} diff --git a/plugins/base/src/test/kotlin/utils/TestUtils.kt b/plugins/base/src/test/kotlin/utils/TestUtils.kt new file mode 100644 index 00000000..bd0e1fe2 --- /dev/null +++ b/plugins/base/src/test/kotlin/utils/TestUtils.kt @@ -0,0 +1,79 @@ +package utils + +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.model.doc.* +import org.jetbrains.dokka.model.doc.P +import org.jetbrains.dokka.testApi.testRunner.AbstractCoreTest +import org.junit.jupiter.api.Assertions.assertTrue +import kotlin.collections.orEmpty + +@DslMarker +annotation class TestDSL + +@TestDSL +abstract class ModelDSL : AbstractCoreTest() { + operator fun Documentable?.div(name: String): Documentable? = + this?.children?.find { it.name == name } + + inline fun <reified T : Documentable> Documentable?.cast(): T = + (this as? T).assertNotNull() +} + +@TestDSL +interface AssertDSL { + infix fun Any?.equals(other: Any?) = this.assertEqual(other) + infix fun Collection<Any>?.allEquals(other: Any?) = + this?.also { c -> c.forEach { it equals other } } ?: run { assert(false) { "Collection is empty" } } + infix fun <T> Collection<T>?.exists(e: T) { + assertTrue(this.orEmpty().isNotEmpty(), "Collection cannot be null or empty") + assertTrue(this!!.any{it == e}, "Collection doesn't contain $e") + } + + infix fun <T> Collection<T>?.counts(n: Int) = this.orEmpty().assertCount(n) + + infix fun <T> T?.notNull(name: String): T = this.assertNotNull(name) + + fun <T> Collection<T>.assertCount(n: Int, prefix: String = "") = + assert(count() == n) { "${prefix}Expected $n, got ${count()}" } + + fun <T> T?.assertEqual(expected: T, prefix: String = "") = assert(this == expected) { + "${prefix}Expected $expected, got $this" + } +} + +inline fun <reified T : Any> Any?.assertIsInstance(name: String): T = + this.let { it as? T } ?: throw AssertionError("$name should not be null") + +fun TagWrapper.text(): String = when (val t = this) { + is NamedTagWrapper -> "${t.name}: [${t.root.text()}]" + else -> t.root.text() +} + +fun DocTag.text(): String = when (val t = this) { + is Text -> t.body + is Code -> t.children.joinToString("\n") { it.text() } + is P -> t.children.joinToString(separator = "\n") { it.text() } + else -> t.toString() +} + +fun <T : Documentable> T?.comments(): String = docs().map { it.text() } + .joinToString(separator = "\n") { it } + +fun <T> T?.assertNotNull(name: String = ""): T = this ?: throw AssertionError("$name should not be null") + +fun <T : Documentable> T?.docs() = this?.documentation.orEmpty().values.flatMap { it.children } + +val DClass.supers + get() = supertypes.flatMap { it.component2() } + +val Bound.name: String? + get() = when (this) { + is Nullable -> inner.name + is OtherParameter -> name + is PrimitiveJavaType -> name + is TypeConstructor -> dri.classNames + is JavaObject -> "Object" + is Void -> "void" + is Dynamic -> "dynamic" + is UnresolvedBound -> "<ERROR CLASS>" + }
\ No newline at end of file diff --git a/plugins/base/src/test/kotlin/utils/contentUtils.kt b/plugins/base/src/test/kotlin/utils/contentUtils.kt new file mode 100644 index 00000000..7fcd8e89 --- /dev/null +++ b/plugins/base/src/test/kotlin/utils/contentUtils.kt @@ -0,0 +1,243 @@ +package utils + +import matchers.content.* +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.pages.ContentGroup +import kotlin.text.Typography.nbsp + +//TODO: Try to unify those functions after update to 1.4 +fun ContentMatcherBuilder<*>.functionSignature( + annotations: Map<String, Set<String>>, + visibility: String, + modifier: String, + keywords: Set<String>, + name: String, + returnType: String? = null, + vararg params: Pair<String, ParamAttributes> +) = + platformHinted { + bareSignature(annotations, visibility, modifier, keywords, name, returnType, *params) + } + +fun ContentMatcherBuilder<*>.bareSignature( + annotations: Map<String, Set<String>>, + visibility: String, + modifier: String, + keywords: Set<String>, + name: String, + returnType: String? = null, + vararg params: Pair<String, ParamAttributes> +) = group { + annotations.entries.forEach { + group { + unwrapAnnotation(it) + } + } + +("$visibility $modifier ${keywords.joinToString("") { "$it " }} fun") + link { +name } + +"(" + params.forEachIndexed { id, (n, t) -> + + t.annotations.forEach { + unwrapAnnotation(it) + } + t.keywords.forEach { + +it + } + + +"$n:" + group { link { +(t.type) } } + if (id != params.lastIndex) + +", " + } + +")" + if (returnType != null) { + +(": ") + group { + link { + +(returnType) + } + } + } +} + +fun ContentMatcherBuilder<*>.functionSignatureWithReceiver( + annotations: Map<String, Set<String>>, + visibility: String?, + modifier: String?, + keywords: Set<String>, + receiver: String, + name: String, + returnType: String? = null, + vararg params: Pair<String, ParamAttributes> +) = + platformHinted { + bareSignatureWithReceiver(annotations, visibility, modifier, keywords, receiver, name, returnType, *params) + } + +fun ContentMatcherBuilder<*>.bareSignatureWithReceiver( + annotations: Map<String, Set<String>>, + visibility: String?, + modifier: String?, + keywords: Set<String>, + receiver: String, + name: String, + returnType: String? = null, + vararg params: Pair<String, ParamAttributes> +) = group { // TODO: remove it when double wrapping for signatures will be resolved + annotations.entries.forEach { + group { + unwrapAnnotation(it) + } + } + +("$visibility $modifier ${keywords.joinToString("") { "$it " }} fun") + group { + link { +receiver } + } + +"." + link { +name } + +"(" + params.forEachIndexed { id, (n, t) -> + + t.annotations.forEach { + unwrapAnnotation(it) + } + t.keywords.forEach { + +it + } + + +"$n:" + group { link { +(t.type) } } + if (id != params.lastIndex) + +", " + } + +")" + if (returnType != null) { + +(": ") + group { + link { + +(returnType) + } + } + } +} + +fun ContentMatcherBuilder<*>.propertySignature( + annotations: Map<String, Set<String>>, + visibility: String, + modifier: String, + keywords: Set<String>, + preposition: String, + name: String, + type: String? = null +) { + group { + header { +"Package test" } + skipAllNotMatching() + } + group { + group { + skipAllNotMatching() + header { +"Properties" } + table { + group { + link { +name } + platformHinted { + group { + annotations.entries.forEach { + group { + unwrapAnnotation(it) + } + } + +("$visibility $modifier ${keywords.joinToString("") { "$it " }} $preposition") + link { +name } + if (type != null) { + +(": ") + group { + link { + +(type) + } + } + } + } + } + } + } + } + } +} + + +fun ContentMatcherBuilder<*>.typealiasSignature(name: String, expressionTarget: String) { + group { + header { +"Package test" } + skipAllNotMatching() + } + group { + group { + skipAllNotMatching() + header { +"Types" } + table { + group { + link { +name } + divergentGroup { + divergentInstance { + group { + group { + group { + group { + +"typealias " + group { + link { +name } + skipAllNotMatching() + } + +" = " + group { + link { +expressionTarget } + } + } + } + } + } + } + skipAllNotMatching() + } + } + skipAllNotMatching() + } + skipAllNotMatching() + } + } +} + +fun ContentMatcherBuilder<*>.pWrapped(text: String) = + group {// TODO: remove it when double wrapping for descriptions will be resolved + group { +text } + } + +fun ContentMatcherBuilder<*>.unnamedTag(tag: String, content: ContentMatcherBuilder<ContentGroup>.() -> Unit) = + group { + header(4) { +tag } + group { content() } + } + +fun ContentMatcherBuilder<*>.unwrapAnnotation(elem: Map.Entry<String, Set<String>>) { + group { + +"@" + link { +elem.key } + +"(" + elem.value.forEach { + group { + +("$it = ") + skipAllNotMatching() + } + } + +")" + } +} + +data class ParamAttributes( + val annotations: Map<String, Set<String>>, + val keywords: Set<String>, + val type: String +) diff --git a/plugins/base/src/test/resources/linkable/includes/include1.md b/plugins/base/src/test/resources/linkable/includes/include1.md new file mode 100644 index 00000000..03d9037d --- /dev/null +++ b/plugins/base/src/test/resources/linkable/includes/include1.md @@ -0,0 +1,7 @@ +# Module example + +This is JVM documentation for module example + +# Package example + +This is JVM documentation for package example
\ No newline at end of file diff --git a/plugins/base/src/test/resources/linkable/includes/include2.md b/plugins/base/src/test/resources/linkable/includes/include2.md new file mode 100644 index 00000000..1574003d --- /dev/null +++ b/plugins/base/src/test/resources/linkable/includes/include2.md @@ -0,0 +1,7 @@ +# Module example + +This is JS documentation for module example + +# Package greeteer + +This is JS documentation for package greeteer
\ No newline at end of file diff --git a/plugins/base/src/test/resources/linkable/samples/jsMain/kotlin/JsClass.kt b/plugins/base/src/test/resources/linkable/samples/jsMain/kotlin/JsClass.kt new file mode 100644 index 00000000..b61ce704 --- /dev/null +++ b/plugins/base/src/test/resources/linkable/samples/jsMain/kotlin/JsClass.kt @@ -0,0 +1,9 @@ +package p2 + +class JsClass { + + /** + * @sample samples.SamplesJs.exampleUsage + */ + fun printWithExclamation(msg: String) = println(msg + "!") +}
\ No newline at end of file diff --git a/plugins/base/src/test/resources/linkable/samples/jsMain/resources/Samples.kt b/plugins/base/src/test/resources/linkable/samples/jsMain/resources/Samples.kt new file mode 100644 index 00000000..55be0ad8 --- /dev/null +++ b/plugins/base/src/test/resources/linkable/samples/jsMain/resources/Samples.kt @@ -0,0 +1,10 @@ +package samples + +import p2.JsClass + +class SamplesJs { + + fun exampleUsage() { + JsClass().printWithExclamation("Hi, Js") + } +}
\ No newline at end of file diff --git a/plugins/base/src/test/resources/linkable/samples/jvmMain/kotlin/JvmClass.kt b/plugins/base/src/test/resources/linkable/samples/jvmMain/kotlin/JvmClass.kt new file mode 100644 index 00000000..960184e6 --- /dev/null +++ b/plugins/base/src/test/resources/linkable/samples/jvmMain/kotlin/JvmClass.kt @@ -0,0 +1,9 @@ +package p2 + +class JvmClass { + + /** + * @sample samples.SamplesJvm.exampleUsage + */ + fun printWithExclamation(msg: String) = println(msg + "!") +}
\ No newline at end of file diff --git a/plugins/base/src/test/resources/linkable/samples/jvmMain/resources/Samples.kt b/plugins/base/src/test/resources/linkable/samples/jvmMain/resources/Samples.kt new file mode 100644 index 00000000..69418fa9 --- /dev/null +++ b/plugins/base/src/test/resources/linkable/samples/jvmMain/resources/Samples.kt @@ -0,0 +1,10 @@ +package samples + +import p2.JvmClass + +class SamplesJvm { + + fun exampleUsage() { + JvmClass().printWithExclamation("Hi, Jvm") + } +}
\ No newline at end of file diff --git a/plugins/base/src/test/resources/linkable/sources/jsMain/kotlin/JsClass.kt b/plugins/base/src/test/resources/linkable/sources/jsMain/kotlin/JsClass.kt new file mode 100644 index 00000000..00dd009b --- /dev/null +++ b/plugins/base/src/test/resources/linkable/sources/jsMain/kotlin/JsClass.kt @@ -0,0 +1,3 @@ +package p1 + +class JsClass
\ No newline at end of file diff --git a/plugins/base/src/test/resources/linkable/sources/jvmMain/kotlin/JvmClass.kt b/plugins/base/src/test/resources/linkable/sources/jvmMain/kotlin/JvmClass.kt new file mode 100644 index 00000000..2113c589 --- /dev/null +++ b/plugins/base/src/test/resources/linkable/sources/jvmMain/kotlin/JvmClass.kt @@ -0,0 +1,3 @@ +package p1 + +class JvmClass
\ No newline at end of file diff --git a/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/commonMain/kotlin/Clock.kt b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/commonMain/kotlin/Clock.kt new file mode 100644 index 00000000..4753cb32 --- /dev/null +++ b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/commonMain/kotlin/Clock.kt @@ -0,0 +1,15 @@ +package example + +/** + * Documentation for expected class Clock + * in common module + */ +expect open class Clock() { + fun getTime(): String + /** + * Time in minis + */ + fun getTimesInMillis(): String + fun getYear(): String +} + diff --git a/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/commonMain/kotlin/House.kt b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/commonMain/kotlin/House.kt new file mode 100644 index 00000000..c879dee7 --- /dev/null +++ b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/commonMain/kotlin/House.kt @@ -0,0 +1,24 @@ +package example + +class House(val street: String, val number: Int) { + + /** + * The owner of the house + */ + var owner: String = "" + + /** + * The owner of the house + */ + val differentOwner: String = "" + + fun addFloor() {} + + class Basement { + val pickles : List<Any> = mutableListOf() + } + + companion object { + val DEFAULT = House("",0) + } +}
\ No newline at end of file diff --git a/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jsMain/kotlin/Clock.kt b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jsMain/kotlin/Clock.kt new file mode 100644 index 00000000..51b8fdc6 --- /dev/null +++ b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jsMain/kotlin/Clock.kt @@ -0,0 +1,28 @@ +package example + +import greeteer.Greeter +import kotlin.js.Date + +/** + * Documentation for actual class Clock in JS + */ +actual open class Clock { + actual fun getTime() = Date.now().toString() + fun onlyJsFunction(): Int = 42 + + /** + * JS implementation of getTimeInMillis + */ + actual fun getTimesInMillis(): String = Date.now().toString() + + /** + * JS custom kdoc + */ + actual fun getYear(): String { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} + +fun main() { + Greeter().greet().also { println(it) } +}
\ No newline at end of file diff --git a/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jvmAndJsSecondCommonMain/kotlin/Greeter.kt b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jvmAndJsSecondCommonMain/kotlin/Greeter.kt new file mode 100644 index 00000000..8a52e2f3 --- /dev/null +++ b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jvmAndJsSecondCommonMain/kotlin/Greeter.kt @@ -0,0 +1,10 @@ +package greeteer + +import example.Clock + +class Greeter { + /** + * Some docs for the [greet] function + */ + fun greet() = Clock().let{ "Hello there! THe time is ${it.getTime()}" } +}
\ No newline at end of file diff --git a/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jvmMain/kotlin/example/Clock.kt b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jvmMain/kotlin/example/Clock.kt new file mode 100644 index 00000000..fec06207 --- /dev/null +++ b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jvmMain/kotlin/example/Clock.kt @@ -0,0 +1,41 @@ +package example + +import greeteer.Greeter + +/** + * Documentation for actual class Clock in JVM + */ +actual open class Clock { + actual fun getTime(): String = System.currentTimeMillis().toString() + actual fun getTimesInMillis(): String = System.currentTimeMillis().toString() + + /** + * Documentation for onlyJVMFunction on... + * wait for it... + * ...JVM! + */ + fun onlyJVMFunction(): Double = 2.5 + /** + * Custom equals function + */ + override fun equals(other: Any?): Boolean { + return super.equals(other) + } + + open fun getDayOfTheWeek(): String { + TODO("not implemented") + } + + /** + * JVM custom kdoc + */ + actual fun getYear(): String { + TODO("not implemented") + } +} + +fun clockList() = listOf(Clock()) + +fun main() { + Greeter().greet().also { println(it) } +}
\ No newline at end of file diff --git a/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jvmMain/kotlin/example/ClockDays.kt b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jvmMain/kotlin/example/ClockDays.kt new file mode 100644 index 00000000..136ae5c8 --- /dev/null +++ b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jvmMain/kotlin/example/ClockDays.kt @@ -0,0 +1,15 @@ +package example + +/** + * frgergergrthe + * */ +enum class ClockDays { + /** + * dfsdfsdfds + * */ + FIRST, + SECOND, // test2 + THIRD, // test3 + FOURTH, // test4 + FIFTH // test5 +}
\ No newline at end of file diff --git a/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jvmMain/kotlin/example/ParticularClock.kt b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jvmMain/kotlin/example/ParticularClock.kt new file mode 100644 index 00000000..40813b50 --- /dev/null +++ b/plugins/base/src/test/resources/multiplatform/basicMultiplatformTest/jvmMain/kotlin/example/ParticularClock.kt @@ -0,0 +1,32 @@ +package example + +import greeteer.Greeter + +class ParticularClock(private val clockDay: ClockDays) : Clock() { + + /** + * Rings bell [times] + */ + fun ringBell(times: Int) {} + + /** + * Uses provider [greeter] + */ + fun useGreeter(greeter: Greeter) { + + } + + /** + * Day of the week + */ + override fun getDayOfTheWeek() = clockDay.name +} + +/** + * A sample extension function + * When $a \ne 0$, there are two solutions to \(ax^2 + bx + c = 0\) and they are $$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$ + * @usesMathJax + */ +fun Clock.extensionFun() { + +}
\ No newline at end of file |