From 8e5c63d035ef44a269b8c43430f43f5c8eebfb63 Mon Sep 17 00:00:00 2001 From: Ignat Beresnev Date: Fri, 10 Nov 2023 11:46:54 +0100 Subject: Restructure the project to utilize included builds (#3174) * Refactor and simplify artifact publishing * Update Gradle to 8.4 * Refactor and simplify convention plugins and build scripts Fixes #3132 --------- Co-authored-by: Adam <897017+aSemy@users.noreply.github.com> Co-authored-by: Oleg Yukhnevich --- ...ntextModuleAndPackageDocumentationReaderTest.kt | 27 + .../transformers/CommentsToContentConverterTest.kt | 484 +++++++++++ ...textModuleAndPackageDocumentationReaderTest1.kt | 187 +++++ ...textModuleAndPackageDocumentationReaderTest3.kt | 61 ++ .../test/kotlin/transformers/DivisionSwitchTest.kt | 126 +++ ...itedEntriesDocumentableFilterTransfromerTest.kt | 162 ++++ ...ntentModuleAndPackageDocumentationReaderTest.kt | 100 +++ .../MergeImplicitExpectActualDeclarationsTest.kt | 386 +++++++++ ...ackageDocumentationTransformerFunctionalTest.kt | 137 +++ ...leAndPackageDocumentationTransformerUnitTest.kt | 260 ++++++ ...sAndInheritedFunctionsDocumentableFilterTest.kt | 229 +++++ .../ReportUndocumentedTransformerTest.kt | 927 +++++++++++++++++++++ .../transformers/SourceLinkTransformerTest.kt | 131 +++ .../kotlin/transformers/SuppressTagFilterTest.kt | 211 +++++ ...nfigurationDocumentableFilterTransformerTest.kt | 193 +++++ .../test/kotlin/transformers/isExceptionTest.kt | 147 ++++ 16 files changed, 3768 insertions(+) create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/AbstractContextModuleAndPackageDocumentationReaderTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest1.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest3.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/DivisionSwitchTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/InheritedEntriesDocumentableFilterTransfromerTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/InvalidContentModuleAndPackageDocumentationReaderTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerFunctionalTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerUnitTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/ObviousAndInheritedFunctionsDocumentableFilterTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/ReportUndocumentedTransformerTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/SourceLinkTransformerTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressTagFilterTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressedByConfigurationDocumentableFilterTransformerTest.kt create mode 100644 dokka-subprojects/plugin-base/src/test/kotlin/transformers/isExceptionTest.kt (limited to 'dokka-subprojects/plugin-base/src/test/kotlin/transformers') diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/AbstractContextModuleAndPackageDocumentationReaderTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/AbstractContextModuleAndPackageDocumentationReaderTest.kt new file mode 100644 index 00000000..8ce9360f --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/AbstractContextModuleAndPackageDocumentationReaderTest.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package transformers + +import org.jetbrains.dokka.model.SourceSetDependent +import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.dokka.model.doc.Text +import org.jetbrains.dokka.model.withDescendants +import org.junit.jupiter.api.io.TempDir +import java.nio.file.Path + +abstract class AbstractContextModuleAndPackageDocumentationReaderTest { + @TempDir + protected lateinit var temporaryDirectory: Path + + + companion object { + val SourceSetDependent.texts: List + get() = values.flatMap { it.withDescendants() } + .flatMap { it.children } + .flatMap { it.children } + .mapNotNull { it as? Text } + .map { it.body } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt new file mode 100644 index 00000000..1387c0e0 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/CommentsToContentConverterTest.kt @@ -0,0 +1,484 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package transformers + +import matchers.content.* +import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.doc.* +import org.jetbrains.dokka.pages.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class CommentsToContentConverterTest { + private val converter = DocTagToContentConverter() + + private fun executeTest( + docTag: DocTag, + match: ContentMatcherBuilder.() -> 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) { + group { +"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) { + group { + +"This is simple test of string" + node() + +"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) { + group { + group { +"Paragraph number one" } + group { + +"Paragraph" + node() + +"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 { + group { + +"list item 1 continue 1" + } + group { + +"list item 2" + node() + +"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) { + group { + node { + group { +"Outer first Outer next line" } + group { +"Outer second" } + node { + group { +"Middle first Middle next line" } + group { +"Middle second" } + node { + group { +"Inner first Inner next line" } + } + group { +"Middle third" } + } + group { +"Outer third" } + } + group { +"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) { + group { + header(1) { +"Header 1" } + group { +"Following text" } + group { +"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) { + group { + header(1) { +"Header 1" } + group { +"Text 1" } + header(2) { +"Header 2" } + group { +"Text 2" } + header(3) { +"Header 3" } + group { +"Text 3" } + header(4) { +"Header 4" } + group { +"Text 4" } + header(5) { +"Header 5" } + group { +"Text 5" } + header(6) { +"Header 6" } + group { +"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) { + group { + group { + group { + +"Blockquotes are very handy in email to emulate reply text. This line is part of the same quote." + } + } + group { +"Quote break." } + group { + group { + +"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) { + group { + group { + group { +"text 1 text 2" } + group { + group { +"text 3 text 4" } + } + group { +"text 5" } + } + group { +"Quote break." } + group { + group { +"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) { + group { + node { + +"val x: Int = 0" + node() + +"val y: String = \"Text\"" + node() + node() + +" val z: Boolean = true" + node() + +"for(i in 0..10) {" + node() + +" println(i)" + node() + +"}" + } + group { +"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) { + group { + link { + +"I'm an inline-style link" + check { + assertEquals( + (this as? ContentResolvedLink)?.address ?: error("Link should be resolved"), + "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 { + 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) { + group { + node { + group { +"Outer first Outer next line" } + group { +"Outer second" } + node { + group { +"Middle first Middle next line" } + group { +"Middle second" } + node { + +"Inner first Inner next line" + } + group { +"Middle third" } + } + group { +"Outer third" } + } + group { + +"New paragraph" + } + } + } + } + + @Test + fun `description list`() { + val docTag = + Dl( + listOf( + Dt( + listOf( + Text("description list can have...") + ) + ), + Dt( + listOf( + Text("... two consecutive description terms") + ) + ), + Dd( + listOf( + Text("and usually has some sort of a description, like this one") + ) + ) + ) + ) + + executeTest(docTag) { + composite { + check { + assertTrue(style.contains(ListStyle.DescriptionList), "Expected DL style") + } + group { + check { + assertTrue(style.contains(ListStyle.DescriptionTerm), "Expected DT style") + } + +"description list can have..." + } + group { + check { + assertTrue(style.contains(ListStyle.DescriptionTerm), "Expected DT style") + } + +"... two consecutive description terms" + } + group { + check { + assertTrue(style.contains(ListStyle.DescriptionDetails), "Expected DD style") + } + +"and usually has some sort of a description, like this one" + } + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest1.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest1.kt new file mode 100644 index 00000000..dfb3eff1 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest1.kt @@ -0,0 +1,187 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package transformers + +import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet +import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.dokka.testApi.logger.TestLogger +import org.jetbrains.dokka.utilities.DokkaConsoleLogger +import org.jetbrains.dokka.utilities.LoggingLevel +import testApi.testRunner.TestDokkaConfigurationBuilder +import testApi.testRunner.dModule +import testApi.testRunner.dPackage +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class ContextModuleAndPackageDocumentationReaderTest1 : AbstractContextModuleAndPackageDocumentationReaderTest() { + + + private val includeSourceSetA by lazy { temporaryDirectory.resolve("includeA.md").toFile() } + private val includeSourceSetB by lazy { temporaryDirectory.resolve("includeB.md").toFile() } + + @BeforeTest + fun materializeIncludes() { + includeSourceSetA.writeText( + """ + # Module moduleA + This is moduleA + + # Package sample.a + This is package sample.a\r\n + + # Package noise.b + This will just add some noise + """.trimIndent().replace("\n", "\r\n") + ) + + includeSourceSetB.writeText( + """ + # Module moduleB + This is moduleB + + # Package sample.b + This is package sample.b + + # Package noise.b + This will just add some more noise + """.trimIndent() + ) + } + + private val configurationBuilder = TestDokkaConfigurationBuilder().apply { + moduleName = "moduleA" + } + + private val sourceSetA by configurationBuilder.sourceSet { + name = "sourceSetA" + includes = listOf(includeSourceSetA.canonicalPath) + } + + + private val sourceSetB by configurationBuilder.sourceSet { + name = "sourceSetB" + includes = listOf(includeSourceSetB.canonicalPath) + } + + + private val sourceSetB2 by configurationBuilder.sourceSet { + name = "sourceSetB2" + includes = emptyList() + } + + + private val context by lazy { + DokkaContext.create( + configuration = configurationBuilder.build(), + logger = TestLogger(DokkaConsoleLogger(LoggingLevel.DEBUG)), + pluginOverrides = emptyList() + ) + } + + private val reader by lazy { context.plugin().querySingle { moduleAndPackageDocumentationReader } } + + @Test + fun `assert moduleA with sourceSetA`() { + val documentation = reader.read(dModule(name = "moduleA", sourceSets = setOf(sourceSetA))) + assertEquals( + 1, documentation.keys.size, + "Expected moduleA only containing documentation in a single source set" + ) + assertEquals( + "sourceSetA", documentation.keys.single().sourceSetID.sourceSetName, + "Expected moduleA documentation coming from sourceSetA" + ) + + assertEquals( + "This is moduleA", documentation.texts.single(), + "Expected moduleA documentation being present" + ) + } + + @Test + fun `assert moduleA with no source sets`() { + val documentation = reader.read(dModule("moduleA")) + assertEquals( + emptyMap(), documentation, + "Expected no documentation received for module not declaring a matching sourceSet" + ) + } + + @Test + fun `assert moduleA with unknown source set`() { + assertFailsWith( + "Expected no documentation received for module with unknown sourceSet" + ) { + reader.read( + dModule("moduleA", sourceSets = setOf(configurationBuilder.unattachedSourceSet { name = "unknown" })) + ) + } + } + + @Test + fun `assert moduleA with all sourceSets`() { + val documentation = reader.read(dModule("moduleA", sourceSets = setOf(sourceSetA, sourceSetB, sourceSetB2))) + assertEquals(1, documentation.entries.size, "Expected only one entry from sourceSetA") + assertEquals(sourceSetA, documentation.keys.single(), "Expected only one entry from sourceSetA") + assertEquals("This is moduleA", documentation.texts.single()) + } + + @Test + fun `assert moduleB with sourceSetB and sourceSetB2`() { + val documentation = reader.read(dModule("moduleB", sourceSets = setOf(sourceSetB, sourceSetB2))) + assertEquals(1, documentation.keys.size, "Expected only one entry from sourceSetB") + assertEquals(sourceSetB, documentation.keys.single(), "Expected only one entry from sourceSetB") + assertEquals("This is moduleB", documentation.texts.single()) + } + + @Test + fun `assert sample_A in sourceSetA`() { + val documentation = reader.read(dPackage(DRI("sample.a"), sourceSets = setOf(sourceSetA))) + assertEquals(1, documentation.keys.size, "Expected only one entry from sourceSetA") + assertEquals(sourceSetA, documentation.keys.single(), "Expected only one entry from sourceSetA") + assertEquals("This is package sample.a\\r\\n", documentation.texts.single()) + } + + @Test + fun `assert sample_a_sub in sourceSetA`() { + val documentation = reader.read(dPackage(DRI("sample.a.sub"), sourceSets = setOf(sourceSetA))) + assertEquals( + emptyMap(), documentation, + "Expected no documentation found for different package" + ) + } + + @Test + fun `assert sample_a in sourceSetB`() { + val documentation = reader.read(dPackage(DRI("sample.a"), sourceSets = setOf(sourceSetB))) + assertEquals( + emptyMap(), documentation, + "Expected no documentation found for different sourceSet" + ) + } + + @Test + fun `assert sample_b in sourceSetB`() { + val documentation = reader.read(dPackage(DRI("sample.b"), sourceSets = setOf(sourceSetB))) + assertEquals(1, documentation.keys.size, "Expected only one entry from sourceSetB") + assertEquals(sourceSetB, documentation.keys.single(), "Expected only one entry from sourceSetB") + assertEquals("This is package sample.b", documentation.texts.single()) + } + + @Test + fun `assert sample_b in sourceSetB and sourceSetB2`() { + val documentation = reader.read(dPackage(DRI("sample.b"), sourceSets = setOf(sourceSetB, sourceSetB2))) + assertEquals(1, documentation.keys.size, "Expected only one entry from sourceSetB") + assertEquals(sourceSetB, documentation.keys.single(), "Expected only one entry from sourceSetB") + assertEquals("This is package sample.b", documentation.texts.single()) + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest3.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest3.kt new file mode 100644 index 00000000..ebd5b7eb --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ContextModuleAndPackageDocumentationReaderTest3.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package transformers + +import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.dokka.utilities.DokkaConsoleLogger +import org.jetbrains.dokka.utilities.LoggingLevel +import testApi.testRunner.TestDokkaConfigurationBuilder +import testApi.testRunner.dPackage +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class ContextModuleAndPackageDocumentationReaderTest3 : AbstractContextModuleAndPackageDocumentationReaderTest() { + + private val include by lazy { temporaryDirectory.resolve("include.md").toFile() } + + @BeforeTest + fun materializeInclude() { + include.writeText( + """ + # Package + This is the root package + + # Package [root] + This is also the root package + """.trimIndent() + ) + } + + private val configurationBuilder = TestDokkaConfigurationBuilder() + + private val sourceSet by configurationBuilder.sourceSet { + includes = listOf(include.canonicalPath) + } + + private val context by lazy { + DokkaContext.create( + configuration = configurationBuilder.build(), + logger = DokkaConsoleLogger(LoggingLevel.DEBUG), + pluginOverrides = emptyList() + ) + } + + private val reader by lazy { context.plugin().querySingle { moduleAndPackageDocumentationReader } } + + + @Test + fun `root package is matched by empty string and the root keyword`() { + val documentation = reader.read(dPackage(DRI(""), sourceSets = setOf(sourceSet))) + assertEquals( + listOf("This is the root package", "This is also the root package"), documentation.texts + ) + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/DivisionSwitchTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/DivisionSwitchTest.kt new file mode 100644 index 00000000..fec5fc47 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/DivisionSwitchTest.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package transformers + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.PluginConfigurationImpl +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.pages.ClasslikePageNode +import org.jetbrains.dokka.pages.ContentHeader +import org.jetbrains.dokka.pages.ContentNode +import org.jetbrains.dokka.pages.ContentText +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class DivisionSwitchTest : BaseAbstractTest() { + + private val query = """ + |/src/source0.kt + package package0 + /** + * Documentation for ClassA + */ + class ClassA { + val A: String = "A" + fun a() {} + fun b() {} + } + + /src/source1.kt + package package0 + /** + * Documentation for ClassB + */ + class ClassB : ClassA() { + val B: String = "B" + fun d() {} + fun e() {} + } + """.trimMargin() + + private fun configuration(switchOn: Boolean) = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + suppressObviousFunctions = false + pluginsConfigurations.add( + PluginConfigurationImpl( + DokkaBase::class.qualifiedName!!, + DokkaConfiguration.SerializationFormat.JSON, + """{ "separateInheritedMembers": $switchOn }""", + ) + ) + } + + private fun testClassB(switchOn: Boolean, operation: (ClasslikePageNode) -> Unit) { + testInline( + query, + configuration(switchOn), + cleanupOutput = true + ) { + pagesTransformationStage = { root -> + val classB = root.dfs { it.name == "ClassB" } as? ClasslikePageNode + assertNotNull(classB, "Tested class not found!") + operation(classB) + } + } + } + + private fun ClasslikePageNode.findSectionWithName(name: String) : ContentNode? { + var sectionHeader: ContentHeader? = null + return content.dfs { node -> + node.children.filterIsInstance().any { header -> + header.children.firstOrNull { it is ContentText && it.text == name }?.also { sectionHeader = header } != null + } + }?.children?.dropWhile { child -> child != sectionHeader }?.drop(1)?.firstOrNull() + } + + @Test + fun `should not split inherited and regular methods`() { + testClassB(false) { classB -> + val functions = classB.findSectionWithName("Functions") + assertNotNull(functions, "Functions not found!") + assertEquals(7, functions.children.size, "Incorrect number of functions found") + } + } + + @Test + fun `should not split inherited and regular properties`() { + testClassB(false) { classB -> + val properties = classB.findSectionWithName("Properties") + assertNotNull(properties, "Properties not found!") + assertEquals(2, properties.children.size, "Incorrect number of properties found") + } + } + + @Test + fun `should split inherited and regular methods`() { + testClassB(true) { classB -> + val functions = classB.findSectionWithName("Functions") + val inheritedFunctions = classB.findSectionWithName("Inherited functions") + assertNotNull(functions, "Functions not found!") + assertEquals(2, functions.children.size, "Incorrect number of functions found") + assertNotNull(inheritedFunctions, "Inherited functions not found!") + assertEquals(5, inheritedFunctions.children.size, "Incorrect number of inherited functions found") + } + } + + @Test + fun `should split inherited and regular properties`() { + testClassB(true) { classB -> + val properties = classB.findSectionWithName("Properties") + assertNotNull(properties, "Properties not found!") + assertEquals(1, properties.children.size, "Incorrect number of properties found") + val inheritedProperties = classB.findSectionWithName("Inherited properties") + assertNotNull(inheritedProperties, "Inherited properties not found!") + assertEquals(1, inheritedProperties.children.size, "Incorrect number of inherited properties found") + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/InheritedEntriesDocumentableFilterTransfromerTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/InheritedEntriesDocumentableFilterTransfromerTest.kt new file mode 100644 index 00000000..c07dd5b8 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/InheritedEntriesDocumentableFilterTransfromerTest.kt @@ -0,0 +1,162 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package transformers + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.DEnum +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class InheritedEntriesDocumentableFilterTransformerTest : BaseAbstractTest() { + val suppressingInheritedConfiguration = dokkaConfiguration { + suppressInheritedMembers = true + suppressObviousFunctions = false + sourceSets { + sourceSet { + sourceRoots = listOf("src") + } + } + } + + val nonSuppressingInheritedConfiguration = dokkaConfiguration { + suppressObviousFunctions = false + suppressInheritedMembers = false + sourceSets { + sourceSet { + sourceRoots = listOf("src") + } + } + } + + + @Test + fun `should suppress toString, equals and hashcode but keep custom ones`() { + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + data class Suppressed(val x: String) { + override fun toString(): String { + return "custom" + } + } + """.trimIndent(), + suppressingInheritedConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions } + assertEquals(listOf("toString", "copy", "component1").sorted(), functions.map { it.name }.sorted()) + } + } + } + + @Test + fun `should suppress toString, equals and hashcode`() { + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + data class Suppressed(val x: String) + """.trimIndent(), + suppressingInheritedConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions } + assertEquals(listOf("copy", "component1").sorted(), functions.map { it.name }.sorted()) + } + } + } + + @Test + fun `should also suppress properites`(){ + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + open class Parent { + val parentValue = "String" + } + + class Child : Parent { + + } + """.trimIndent(), + suppressingInheritedConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val properties = modules.flatMap { it.packages }.flatMap { it.classlikes }.first { it.name == "Child" }.properties + assertEquals(0, properties.size) + } + } + } + + @Test + fun `should not suppress properites if config says so`(){ + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + open class Parent { + val parentValue = "String" + } + + class Child : Parent { + + } + """.trimIndent(), + nonSuppressingInheritedConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val properties = modules.flatMap { it.packages }.flatMap { it.classlikes }.first { it.name == "Child" }.properties + assertEquals(listOf("parentValue"), properties.map { it.name }) + } + } + } + + @Test + fun `should work with enum entries`(){ + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + enum class Suppressed { + ENTRY_SUPPRESSED + } + """.trimIndent(), + suppressingInheritedConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val entry = (modules.flatMap { it.packages }.flatMap { it.classlikes }.first { it.name == "Suppressed" } as DEnum).entries.first() + assertEquals(emptyList(), entry.properties) + assertEquals(emptyList(), entry.functions) + assertEquals(emptyList(), entry.classlikes) + } + } + } + + @Test + fun `should work with enum entries when not suppressing`(){ + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + enum class Suppressed { + ENTRY_SUPPRESSED; + class A + } + """.trimIndent(), + nonSuppressingInheritedConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val entry = (modules.flatMap { it.packages }.flatMap { it.classlikes }.first { it.name == "Suppressed" } as DEnum).entries.first() + assertEquals(listOf("name", "ordinal"), entry.properties.map { it.name }) + assertTrue(entry.functions.map { it.name }.containsAll(listOf("compareTo", "equals", "hashCode", "toString"))) + assertEquals(emptyList(), entry.classlikes) + } + } + } +} + diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/InvalidContentModuleAndPackageDocumentationReaderTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/InvalidContentModuleAndPackageDocumentationReaderTest.kt new file mode 100644 index 00000000..ca7536d4 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/InvalidContentModuleAndPackageDocumentationReaderTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package transformers + +import org.jetbrains.dokka.analysis.kotlin.internal.InternalKotlinAnalysisPlugin +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.dokka.utilities.DokkaConsoleLogger +import org.jetbrains.dokka.utilities.LoggingLevel +import testApi.testRunner.TestDokkaConfigurationBuilder +import testApi.testRunner.dModule +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + + +class InvalidContentModuleAndPackageDocumentationReaderTest : AbstractContextModuleAndPackageDocumentationReaderTest() { + + private val includeA by lazy { temporaryDirectory.resolve("includeA.md").toFile() } + private val includeB by lazy { temporaryDirectory.resolve("includeB.md").toFile() } + + @BeforeTest + fun materializeInclude() { + includeA.writeText( + """ + Invalid random stuff + + # Module moduleA + Simple stuff + """.trimIndent() + ) + includeB.writeText( + """ + # Module moduleB + ### + """.trimIndent() + ) + } + + private val configurationBuilderA = TestDokkaConfigurationBuilder().apply { + moduleName = "moduleA" + } + private val configurationBuilderB = TestDokkaConfigurationBuilder().apply { + moduleName = "moduleB" + } + + private val sourceSetA by configurationBuilderA.sourceSet { + includes = listOf(includeA.canonicalPath) + } + + private val sourceSetB by configurationBuilderB.sourceSet { + includes = listOf(includeB.canonicalPath) + } + + private val contextA by lazy { + DokkaContext.create( + configuration = configurationBuilderA.build(), + logger = DokkaConsoleLogger(LoggingLevel.DEBUG), + pluginOverrides = emptyList() + ) + } + private val contextB by lazy { + DokkaContext.create( + configuration = configurationBuilderB.build(), + logger = DokkaConsoleLogger(LoggingLevel.DEBUG), + pluginOverrides = emptyList() + ) + } + + private val readerA by lazy { contextA.plugin().querySingle { moduleAndPackageDocumentationReader } } + private val readerB by lazy { contextB.plugin().querySingle { moduleAndPackageDocumentationReader } } + + + @Test + fun `parsing should fail with a message when documentation is in not proper format`() { + val exception = + runCatching { readerA.read(dModule(name = "moduleA", sourceSets = setOf(sourceSetA))) }.exceptionOrNull() + assertEquals( + "Unexpected classifier: \"Invalid\", expected either \"Module\" or \"Package\". \n" + + "For more information consult the specification: https://kotlinlang.org/docs/dokka-module-and-package-docs.html", + exception?.message + ) + } + + @Test + fun `parsing should fail with a message where it encountered error and why`() { + val exception = + runCatching { readerB.read(dModule(name = "moduleB", sourceSets = setOf(sourceSetB))) }.exceptionOrNull()?.message!! + + //I don't want to assert whole message since it contains a path to a temporary folder + assertTrue(exception.contains("Wrong AST Tree. Header does not contain expected content in ")) + assertTrue(exception.contains("includeB.md")) + assertTrue(exception.contains("element starts from offset 0 and ends 3: ###")) + } +} + diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt new file mode 100644 index 00000000..18e42e47 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/MergeImplicitExpectActualDeclarationsTest.kt @@ -0,0 +1,386 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package transformers + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.PluginConfigurationImpl +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.childrenOfType +import org.jetbrains.dokka.model.dfs +import org.jetbrains.dokka.model.firstChildOfType +import org.jetbrains.dokka.pages.* +import utils.assertNotNull +import utils.findSectionWithName +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class MergeImplicitExpectActualDeclarationsTest : BaseAbstractTest() { + + @Suppress("UNUSED_VARIABLE") + private fun configuration(switchOn: Boolean) = dokkaConfiguration { + sourceSets { + val common = sourceSet { + name = "common" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin/pageMerger/Test.kt") + } + val js = sourceSet { + name = "js" + displayName = "js" + analysisPlatform = "js" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/jsMain/kotlin/pageMerger/Test.kt") + } + val jvm = sourceSet { + name = "jvm" + displayName = "jvm" + analysisPlatform = "jvm" + sourceRoots = listOf("src/jvmMain/kotlin/pageMerger/Test.kt") + } + } + pluginsConfigurations.add( + PluginConfigurationImpl( + DokkaBase::class.qualifiedName!!, + DokkaConfiguration.SerializationFormat.JSON, + """{ "mergeImplicitExpectActualDeclarations": $switchOn }""", + ) + ) + } + + private fun ContentNode.findTabWithType(type: TabbedContentType): ContentNode? = dfs { node -> + node.children.filterIsInstance().any { gr -> + gr.extra[TabbedContentTypeExtra]?.value == type + } + } + + @Test + fun `should merge fun`() { + testInline( + """ + + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class classA { + | fun method1(): String + |} + | + |/src/jsMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class classA { + | fun method1(): Int + |} + | + """.trimMargin(), + configuration(true), + cleanupOutput = true + ) { + pagesTransformationStage = { root -> + val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode + assertNotNull(classPage, "Tested class not found!") + + val functions = classPage.findSectionWithName("Functions").assertNotNull("Functions") + val method1 = functions.children.singleOrNull().assertNotNull("method1") + + assertEquals( + 2, + method1.firstChildOfType().childrenOfType().size, + "Incorrect number of divergent instances found" + ) + + val methodPage = root.dfs { it.name == "method1" } as? MemberPageNode + assertNotNull(methodPage, "Tested method not found!") + + val divergentGroup = methodPage.content.dfs { it is ContentDivergentGroup } as? ContentDivergentGroup + + assertEquals( + 2, + divergentGroup?.childrenOfType()?.size, + "Incorrect number of divergent instances found in method page" + ) + } + } + } + + @Test + fun `should merge class and typealias`() { + testInline( + """ + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class A { + | fun method1(): String + |} + | + |/src/jsMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |typealias A = String + | + """.trimMargin(), + configuration(true), + cleanupOutput = true + ) { + pagesTransformationStage = { root -> + val classPage = root.dfs { it.name == "A" } as? ClasslikePageNode + assertNotNull(classPage, "Tested class not found!") + + val platformHintedContent = classPage.content.dfs { it is PlatformHintedContent }.assertNotNull("platformHintedContent") + assertEquals(2, platformHintedContent.sourceSets.size) + + platformHintedContent.dfs { it is ContentText && it.text == "class " }.assertNotNull("class keyword") + platformHintedContent.dfs { it is ContentText && it.text == "typealias " }.assertNotNull("typealias keyword") + } + } + } + @Test + fun `should merge method and prop`() { + testInline( + """ + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class classA { + | fun method1(): String + |} + | + |/src/jsMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class classA { + | val prop1: Int + |} + | + """.trimMargin(), + configuration(true), + cleanupOutput = true + ) { + pagesTransformationStage = { root -> + val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode + assertNotNull(classPage, "Tested class not found!") + + val props = classPage.findSectionWithName("Properties").assertNotNull("Properties") + props.children.singleOrNull().assertNotNull("prop1") + + val functions = classPage.findSectionWithName("Functions").assertNotNull("Functions") + functions.children.singleOrNull().assertNotNull("method1") + } + } + } + + @Test + fun `should merge prop`() { + testInline( + """ + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class classA { + | val prop1: String + |} + | + |/src/jsMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class classA { + | val prop1: Int + |} + | + """.trimMargin(), + configuration(true), + cleanupOutput = true + ) { + pagesTransformationStage = { root -> + val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode + assertNotNull(classPage, "Tested class not found!") + + val props = classPage.findSectionWithName("Properties").assertNotNull("Properties") + val prop1 = props.children.singleOrNull().assertNotNull("prop1") + + assertEquals( + 2, + prop1.firstChildOfType().children.size, + "Incorrect number of divergent instances found" + ) + + val propPage = root.dfs { it.name == "prop1" } as? MemberPageNode + assertNotNull(propPage, "Tested method not found!") + + val divergentGroup = propPage.content.dfs { it is ContentDivergentGroup } as? ContentDivergentGroup + + assertEquals( + 2, + divergentGroup?.childrenOfType()?.size, + "Incorrect number of divergent instances found in method page" + ) + } + } + } + + @Test + fun `should merge enum and class`() { + testInline( + """ + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class classA { + | val prop1: String + |} + | + |/src/jsMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |enum class classA { + | ENTRY + |} + | + """.trimMargin(), + configuration(true), + cleanupOutput = true + ) { + pagesTransformationStage = { root -> + val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode + assertNotNull(classPage, "Tested class not found!") + + val entries = classPage.content.findTabWithType(BasicTabbedContentType.ENTRY).assertNotNull("Entries") + entries.children.singleOrNull().assertNotNull("ENTRY") + + val props = classPage.findSectionWithName("Properties").assertNotNull("Properties") + assertEquals( + 3, + props.children.size, + "Incorrect number of properties found in method page" + ) + } + } + } + + fun PageNode.childrenRec(): List = listOf(this) + children.flatMap { it.childrenRec() } + + @Test + fun `should merge enum entries`() { + testInline( + """ + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |enum class classA { + | SMTH; + | fun method1(): Int + |} + | + |/src/jsMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |enum class classA { + | SMTH; + | fun method1(): Int + |} + | + """.trimMargin(), + configuration(true), + cleanupOutput = true + ) { + pagesTransformationStage = { root -> + val classPage = root.dfs { it.name == "SMTH" } as? ClasslikePageNode + assertNotNull(classPage, "Tested class not found!") + + val functions = classPage.findSectionWithName("Functions").assertNotNull("Functions") + val method1 = functions.children.single { it.sourceSets.size == 2 && it.dci.dri.singleOrNull()?.callable?.name == "method1" } + .assertNotNull("method1") + + assertEquals( + 2, + method1.firstChildOfType().childrenOfType().size, + "Incorrect number of divergent instances found" + ) + } + } + } + + /** + * There is a case when a property and fun from different source sets + * have the same name so pages have the same urls respectively. + */ + @Test + fun `should no merge prop and method with the same name`() { + testInline( + """ + |/src/jvmMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class classA { + | fun merged():String + |} + | + |/src/jsMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |class classA { + | val merged:String + |} + | + """.trimMargin(), + configuration(true), + cleanupOutput = true + ) { + pagesTransformationStage = { root -> + val allChildren = root.childrenRec().filterIsInstance() + + assertEquals( + 1, + allChildren.filter { it.name == "merged" }.size, + "Incorrect number of fun pages" + ) + } + } + } + + @Test + fun `should always merge constructor`() { + testInline( + """ + |/src/commonMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |expect class classA(a: Int) + | + |/src/jsMain/kotlin/pageMerger/Test.kt + |package pageMerger + | + |actual class classA(a: Int) + """.trimMargin(), + configuration(false), + cleanupOutput = true + ) { + pagesTransformationStage = { root -> + val classPage = root.dfs { it.name == "classA" } as? ClasslikePageNode + assertNotNull(classPage, "Tested class not found!") + + val constructors = classPage.findSectionWithName("Constructors").assertNotNull("Constructors") + + assertEquals( + 1, + constructors.children.size, + "Incorrect number of constructors" + ) + + val platformHinted = constructors.dfs { it is PlatformHintedContent } as? PlatformHintedContent + + assertEquals( + 2, + platformHinted?.sourceSets?.size, + "Incorrect number of source sets" + ) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerFunctionalTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerFunctionalTest.kt new file mode 100644 index 00000000..54f0120a --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerFunctionalTest.kt @@ -0,0 +1,137 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package transformers + +import org.jetbrains.dokka.DokkaSourceSetID +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.junit.jupiter.api.io.TempDir +import transformers.AbstractContextModuleAndPackageDocumentationReaderTest.Companion.texts +import utils.OnlyDescriptorsMPP +import java.nio.file.Path +import kotlin.test.Test +import kotlin.test.assertEquals + +class ModuleAndPackageDocumentationTransformerFunctionalTest : BaseAbstractTest() { + + @OnlyDescriptorsMPP("#3238") + @Test + fun `multiplatform project`(@TempDir tempDir: Path) { + val include = tempDir.resolve("include.md").toFile() + include.writeText( + """ + # Module moduleA + This is moduleA + + # Package + This is the root package + + # Package [root] + This is also the root package + + # Package common + This is the common package + + # Package jvm + This is the jvm package + + # Package js + This is the js package + """.trimIndent() + ) + val configuration = dokkaConfiguration { + moduleName = "moduleA" + sourceSets { + sourceSet { + name = "commonMain" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin") + includes = listOf(include.canonicalPath) + } + sourceSet { + name = "jsMain" + displayName = "js" + analysisPlatform = "js" + sourceRoots = listOf("src/jsMain/kotlin") + dependentSourceSets = setOf(DokkaSourceSetID("moduleA", "commonMain")) + includes = listOf(include.canonicalPath) + } + sourceSet { + name = "jvmMain" + displayName = "jvm" + analysisPlatform = "jvm" + sourceRoots = listOf("src/jvmMain/kotlin") + dependentSourceSets = setOf(DokkaSourceSetID("moduleA", "commonMain")) + includes = listOf(include.canonicalPath) + } + } + } + + testInline( + """ + /src/commonMain/kotlin/common/CommonApi.kt + package common + val commonApi = "common" + + /src/jsMain/kotlin/js/JsApi.kt + package js + val jsApi = "js" + + /src/jvmMain/kotlin/jvm/JvmApi.kt + package jvm + val jvmApi = "jvm" + + /src/commonMain/kotlin/CommonRoot.kt + val commonRoot = "commonRoot" + + /src/jsMain/kotlin/JsRoot.kt + val jsRoot = "jsRoot" + + /src/jvmMain/kotlin/JvmRoot.kt + val jvmRoot = "jvmRoot" + """.trimIndent(), + configuration + ) { + this.documentablesMergingStage = { module -> + val packageNames = module.packages.map { it.dri.packageName ?: "NULL" } + assertEquals( + listOf("", "common", "js", "jvm").sorted(), packageNames.sorted(), + "Expected