diff options
Diffstat (limited to 'dokka-subprojects/plugin-base/src/test/kotlin/transformers')
16 files changed, 3768 insertions, 0 deletions
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<DocumentationNode>.texts: List<String> + 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<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) { + 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<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) { + group { + group { +"Paragraph number one" } + group { + +"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) { + group { + 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" } + } + 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<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>() + +"}" + } + 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<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) { + group { + 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" } + } + 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<ContentList> { + 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<InternalKotlinAnalysisPlugin>().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<IllegalStateException>( + "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<DokkaSourceSet, DocumentationNode>(), 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<DokkaSourceSet, DocumentationNode>(), 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<InternalKotlinAnalysisPlugin>().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<ContentHeader>().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<InternalKotlinAnalysisPlugin>().querySingle { moduleAndPackageDocumentationReader } } + private val readerB by lazy { contextB.plugin<InternalKotlinAnalysisPlugin>().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<ContentGroup>().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<ContentDivergentGroup>().childrenOfType<ContentDivergentInstance>().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<ContentDivergentInstance>()?.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<ContentDivergentGroup>().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<ContentDivergentInstance>()?.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<PageNode> = 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<ContentDivergentGroup>().childrenOfType<ContentDivergentInstance>().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<MemberPageNode>() + + 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 all packages to be present" + ) + + /* Assert module documentation */ + assertEquals(3, module.documentation.keys.size, "Expected all three source sets") + assertEquals("This is moduleA", module.documentation.texts.distinct().joinToString()) + + /* Assert root package */ + val rootPackage = module.packages.single { it.dri.packageName == "" } + assertEquals(3, rootPackage.documentation.keys.size, "Expected all three source sets") + assertEquals( + listOf("This is the root package", "This is also the root package"), + rootPackage.documentation.texts.distinct() + ) + + /* Assert common package */ + val commonPackage = module.packages.single { it.dri.packageName == "common" } + assertEquals(3, commonPackage.documentation.keys.size, "Expected all three source sets") + assertEquals("This is the common package", commonPackage.documentation.texts.distinct().joinToString()) + + /* Assert js package */ + val jsPackage = module.packages.single { it.dri.packageName == "js" } + assertEquals( + "This is the js package", + jsPackage.documentation.texts.joinToString() + ) + + /* Assert the jvm package */ + val jvmPackage = module.packages.single { it.dri.packageName == "jvm" } + assertEquals( + "This is the jvm package", + jvmPackage.documentation.texts.joinToString() + ) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerUnitTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerUnitTest.kt new file mode 100644 index 00000000..a54b6c68 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ModuleAndPackageDocumentationTransformerUnitTest.kt @@ -0,0 +1,260 @@ +/* + * 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.analysis.kotlin.internal.ModuleAndPackageDocumentationReader +import org.jetbrains.dokka.analysis.markdown.jb.MARKDOWN_ELEMENT_FILE_NAME +import org.jetbrains.dokka.base.transformers.documentables.ModuleAndPackageDocumentationTransformer +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.model.DModule +import org.jetbrains.dokka.model.DPackage +import org.jetbrains.dokka.model.SourceSetDependent +import org.jetbrains.dokka.model.doc.CustomDocTag +import org.jetbrains.dokka.model.doc.Description +import org.jetbrains.dokka.model.doc.DocumentationNode +import org.jetbrains.dokka.model.doc.Text +import testApi.testRunner.dPackage +import testApi.testRunner.sourceSet +import kotlin.test.Test +import kotlin.test.assertEquals + + +class ModuleAndPackageDocumentationTransformerUnitTest { + + @Test + fun `empty list of modules`() { + val transformer = ModuleAndPackageDocumentationTransformer( + object : ModuleAndPackageDocumentationReader { + override fun read(module: DModule): SourceSetDependent<DocumentationNode> = throw NotImplementedError() + override fun read(pkg: DPackage): SourceSetDependent<DocumentationNode> = throw NotImplementedError() + override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode = throw NotImplementedError() + } + ) + + assertEquals( + emptyList<DModule>(), transformer(emptyList()), + ) + } + + @Test + fun `single module documentation`() { + val transformer = ModuleAndPackageDocumentationTransformer( + object : ModuleAndPackageDocumentationReader { + override fun read(pkg: DPackage): SourceSetDependent<DocumentationNode> = throw NotImplementedError() + override fun read(module: DModule): SourceSetDependent<DocumentationNode> { + return module.sourceSets.associateWith { sourceSet -> + documentationNode("doc" + sourceSet.displayName) + } + } + override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode = throw NotImplementedError() + } + ) + + val result = transformer( + listOf( + DModule( + "ModuleName", + documentation = emptyMap(), + packages = emptyList(), + sourceSets = setOf( + sourceSet("A"), + sourceSet("B") + ) + ) + ) + ) + + assertEquals( + DModule( + "ModuleName", + documentation = mapOf( + sourceSet("A") to documentationNode("docA"), + sourceSet("B") to documentationNode("docB") + ), + sourceSets = setOf(sourceSet("A"), sourceSet("B")), + packages = emptyList() + ), + result.single() + ) + + } + + @Test + fun `merges with already existing module documentation`() { + val transformer = ModuleAndPackageDocumentationTransformer( + object : ModuleAndPackageDocumentationReader { + override fun read(pkg: DPackage): SourceSetDependent<DocumentationNode> = throw NotImplementedError() + override fun read(module: DModule): SourceSetDependent<DocumentationNode> { + /* Only add documentation for first source set */ + return module.sourceSets.take(1).associateWith { sourceSet -> + documentationNode("doc" + sourceSet.displayName) + } + } + override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode = throw NotImplementedError() + } + ) + + val result = transformer( + listOf( + DModule( + "MyModule", + documentation = mapOf( + sourceSet("A") to documentationNode("pre-existing:A"), + sourceSet("B") to documentationNode("pre-existing:B") + ), + sourceSets = setOf(sourceSet("A"), sourceSet("B")), + packages = emptyList() + ) + ) + ) + + assertEquals( + DModule( + "MyModule", + documentation = mapOf( + /* Expect previous documentation and newly attached one */ + sourceSet("A") to documentationNode("pre-existing:A", "docA"), + /* Only first source set will get documentation attached */ + sourceSet("B") to documentationNode("pre-existing:B") + ), + sourceSets = setOf(sourceSet("A"), sourceSet("B")), + packages = emptyList() + ), + result.single() + ) + } + + @Test + fun `package documentation`() { + val transformer = ModuleAndPackageDocumentationTransformer( + object : ModuleAndPackageDocumentationReader { + override fun read(module: DModule): SourceSetDependent<DocumentationNode> = emptyMap() + override fun read(pkg: DPackage): SourceSetDependent<DocumentationNode> { + /* Only attach documentation to packages with 'attach' */ + if ("attach" !in pkg.dri.packageName.orEmpty()) return emptyMap() + /* Only attach documentation to two source sets */ + return pkg.sourceSets.take(2).associateWith { sourceSet -> + documentationNode("doc:${sourceSet.displayName}:${pkg.dri.packageName}") + } + } + override fun read(module: DokkaConfiguration.DokkaModuleDescription): DocumentationNode = throw NotImplementedError() + } + ) + + val result = transformer( + listOf( + DModule( + "MyModule", + documentation = emptyMap(), + sourceSets = emptySet(), + packages = listOf( + dPackage( + dri = DRI("com.sample"), + documentation = mapOf( + sourceSet("A") to documentationNode("pre-existing:A:com.sample") + ), + sourceSets = setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")), + ), + dPackage( + dri = DRI("com.attach"), + documentation = mapOf( + sourceSet("A") to documentationNode("pre-existing:A:com.attach") + ), + sourceSets = setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")) + ), + dPackage( + dri = DRI("com.attach.sub"), + documentation = mapOf( + sourceSet("A") to documentationNode("pre-existing:A:com.attach.sub"), + sourceSet("B") to documentationNode("pre-existing:B:com.attach.sub"), + sourceSet("C") to documentationNode("pre-existing:C:com.attach.sub") + ), + sourceSets = setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")), + ) + ) + ) + ) + ) + + result.single().packages.forEach { pkg -> + assertEquals( + setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")), pkg.sourceSets, + "Expected source sets A, B, C for package ${pkg.dri.packageName}" + ) + } + + val comSample = result.single().packages.single { it.dri.packageName == "com.sample" } + assertEquals( + mapOf(sourceSet("A") to documentationNode("pre-existing:A:com.sample")), + comSample.documentation, + "Expected no documentation added to package 'com.sample' because of wrong package" + ) + + val comAttach = result.single().packages.single { it.dri.packageName == "com.attach" } + assertEquals( + mapOf( + sourceSet("A") to documentationNode("pre-existing:A:com.attach", "doc:A:com.attach"), + sourceSet("B") to documentationNode("doc:B:com.attach") + ), + comAttach.documentation, + "Expected documentation added to source sets A and B" + ) + + assertEquals( + DModule( + "MyModule", + documentation = emptyMap(), + sourceSets = emptySet(), + packages = listOf( + dPackage( + dri = DRI("com.sample"), + documentation = mapOf( + /* No documentation added, since in wrong package */ + sourceSet("A") to documentationNode("pre-existing:A:com.sample") + ), + sourceSets = setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")), + + ), + dPackage( + dri = DRI("com.attach"), + documentation = mapOf( + /* Documentation added */ + sourceSet("A") to documentationNode("pre-existing:A:com.attach", "doc:A:com.attach"), + sourceSet("B") to documentationNode("doc:B:com.attach") + ), + sourceSets = setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")), + ), + dPackage( + dri = DRI("com.attach.sub"), + documentation = mapOf( + /* Documentation added */ + sourceSet("A") to documentationNode( + "pre-existing:A:com.attach.sub", + "doc:A:com.attach.sub" + ), + /* Documentation added */ + sourceSet("B") to documentationNode( + "pre-existing:B:com.attach.sub", + "doc:B:com.attach.sub" + ), + /* No documentation added, since in wrong source set */ + sourceSet("C") to documentationNode("pre-existing:C:com.attach.sub") + ), + sourceSets = setOf(sourceSet("A"), sourceSet("B"), sourceSet("C")), + ) + ) + ), result.single() + ) + } + + + private fun documentationNode(vararg texts: String): DocumentationNode { + return DocumentationNode( + texts.toList() + .map { Description(CustomDocTag(listOf(Text(it)), name = MARKDOWN_ELEMENT_FILE_NAME)) }) + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ObviousAndInheritedFunctionsDocumentableFilterTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ObviousAndInheritedFunctionsDocumentableFilterTest.kt new file mode 100644 index 00000000..d035948f --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ObviousAndInheritedFunctionsDocumentableFilterTest.kt @@ -0,0 +1,229 @@ +/* + * 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.DokkaConfigurationImpl +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import testApi.testRunner.dokkaConfiguration +import kotlin.test.assertEquals + +class ObviousAndInheritedFunctionsDocumentableFilterTest : BaseAbstractTest() { + companion object { + @JvmStatic + fun suppressingObviousConfiguration() = listOf(dokkaConfiguration { + suppressInheritedMembers = false + suppressObviousFunctions = true + sourceSets { + sourceSet { + sourceRoots = listOf("src") + } + } + }) + + @JvmStatic + fun nonSuppressingObviousConfiguration() = listOf(dokkaConfiguration { + suppressObviousFunctions = false + suppressInheritedMembers = false + sourceSets { + sourceSet { + sourceRoots = listOf("src") + } + } + }) + + @JvmStatic + fun suppressingInheritedConfiguration() = listOf(dokkaConfiguration { + suppressInheritedMembers = true + suppressObviousFunctions = false + sourceSets { + sourceSet { + sourceRoots = listOf("src") + } + } + }) + + @JvmStatic + fun nonSuppressingInheritedConfiguration() = listOf(dokkaConfiguration { + suppressObviousFunctions = false + suppressInheritedMembers = false + sourceSets { + sourceSet { + sourceRoots = listOf("src") + } + } + }) + } + + + @ParameterizedTest + @MethodSource(value = ["suppressingObviousConfiguration"]) + fun `should suppress toString, equals and hashcode`(suppressingConfiguration: DokkaConfigurationImpl) { + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + data class Suppressed(val x: String) + """.trimIndent(), + suppressingConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions } + assertEquals(0, functions.size) + } + } + } + + @ParameterizedTest + @MethodSource(value = ["suppressingObviousConfiguration", "suppressingInheritedConfiguration"]) + fun `should suppress toString, equals and hashcode for interface`(suppressingConfiguration: DokkaConfigurationImpl) { + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + interface Suppressed + """.trimIndent(), + suppressingConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions } + assertEquals(0, functions.size) + } + } + } + + @ParameterizedTest + @MethodSource(value = ["suppressingObviousConfiguration", "suppressingInheritedConfiguration"]) + fun `should suppress toString, equals and hashcode in Java`(suppressingConfiguration: DokkaConfigurationImpl) { + testInline( + """ + /src/suppressed/Suppressed.java + package suppressed; + public class Suppressed { + } + """.trimIndent(), + suppressingConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions } + assertEquals(0, functions.size) + } + } + } + + @ParameterizedTest + @MethodSource(value = ["suppressingObviousConfiguration"]) + fun `should suppress toString, equals and hashcode but keep custom ones`(suppressingConfiguration: DokkaConfigurationImpl) { + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + data class Suppressed(val x: String) { + override fun toString(): String { + return "custom" + } + } + """.trimIndent(), + suppressingConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions } + assertEquals(listOf("toString"), functions.map { it.name }) + } + } + } + + @ParameterizedTest + @MethodSource(value = ["suppressingObviousConfiguration", "suppressingInheritedConfiguration"]) + fun `should suppress toString, equals and hashcode but keep custom ones in Java`(suppressingConfiguration: DokkaConfigurationImpl) { + testInline( + """ + /src/suppressed/Suppressed.java + package suppressed; + public class Suppressed { + @Override + public String toString() { + return ""; + } + } + """.trimIndent(), + suppressingConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions } + assertEquals(listOf("toString"), functions.map { it.name }) + } + } + } + + @ParameterizedTest + @MethodSource(value = ["nonSuppressingObviousConfiguration", "nonSuppressingInheritedConfiguration"]) + fun `should not suppress toString, equals and hashcode if custom config is provided`(nonSuppressingConfiguration: DokkaConfigurationImpl) { + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + data class Suppressed(val x: String) + """.trimIndent(), + nonSuppressingConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions } + assertEquals( + listOf("copy", "equals", "toString", "component1", "hashCode").sorted(), + functions.map { it.name }.sorted() + ) + } + } + } + + @ParameterizedTest + @MethodSource(value = ["nonSuppressingObviousConfiguration", "nonSuppressingInheritedConfiguration"]) + fun `not should suppress toString, equals and hashcode for interface if custom config is provided`(nonSuppressingConfiguration: DokkaConfigurationImpl) { + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + interface Suppressed + """.trimIndent(), + nonSuppressingConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions } + assertEquals(listOf("equals", "hashCode", "toString").sorted(), functions.map { it.name }.sorted()) + } + } + } + + @ParameterizedTest + @MethodSource(value = ["nonSuppressingObviousConfiguration", "nonSuppressingInheritedConfiguration"]) + fun `should not suppress toString, equals and hashcode if custom config is provided in Java`(nonSuppressingConfiguration: DokkaConfigurationImpl) { + testInline( + """ + /src/suppressed/Suppressed.java + package suppressed; + public class Suppressed { + } + """.trimIndent(), + nonSuppressingConfiguration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val functions = modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions } + //I would normally just assert names but this would make it JDK dependent, so this is better + assertEquals( + 5, + setOf( + "equals", + "hashCode", + "toString", + "notify", + "notifyAll" + ).intersect(functions.map { it.name }).size + ) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ReportUndocumentedTransformerTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ReportUndocumentedTransformerTest.kt new file mode 100644 index 00000000..c824e690 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/ReportUndocumentedTransformerTest.kt @@ -0,0 +1,927 @@ +/* + * 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.DokkaDefaults +import org.jetbrains.dokka.PackageOptionsImpl +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertEquals + + +class ReportUndocumentedTransformerTest : BaseAbstractTest() { + @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) + } + } + } + + @Ignore + @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( + matchingRegex = "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( + matchingRegex = "sample.*", + reportUndocumented = false, + ) + perPackageOptions += packageOptions( + matchingRegex = "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( + matchingRegex = "sample.*", + reportUndocumented = true, + ) + perPackageOptions += packageOptions( + matchingRegex = "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 by 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 by 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 by 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( + matchingRegex: String, + reportUndocumented: Boolean?, + includeNonPublic: Boolean = true, + skipDeprecated: Boolean = false, + suppress: Boolean = false, + documentedVisibilities: Set<DokkaConfiguration.Visibility> = DokkaDefaults.documentedVisibilities + ) = PackageOptionsImpl( + matchingRegex = matchingRegex, + reportUndocumented = reportUndocumented, + includeNonPublic = includeNonPublic, + documentedVisibilities = documentedVisibilities, + skipDeprecated = skipDeprecated, + suppress = suppress + ) +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SourceLinkTransformerTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SourceLinkTransformerTest.kt new file mode 100644 index 00000000..87424120 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SourceLinkTransformerTest.kt @@ -0,0 +1,131 @@ +/* + * 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.SourceLinkDefinitionImpl +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jsoup.nodes.Element +import signatures.renderedContent +import utils.TestOutputWriterPlugin +import java.net.URL +import kotlin.test.Test +import kotlin.test.assertEquals + +class SourceLinkTransformerTest : BaseAbstractTest() { + + private fun Element.getSourceLink() = select(".symbol .floating-right") + .select("a[href]") + .attr("href") + + @Test + fun `source link should lead to name`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + sourceLinks = listOf( + SourceLinkDefinitionImpl( + localDirectory = "src/main/kotlin", + remoteUrl = URL("https://github.com/user/repo/tree/master/src/main/kotlin"), + remoteLineSuffix = "#L" + ) + ) + } + } + } + + val writerPlugin = TestOutputWriterPlugin() + + testInline( + """ + |/src/main/kotlin/basic/Deprecated.kt + |package testpackage + | + |/** + |* Marks the annotated declaration as deprecated. ... + |*/ + |@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS) + |@MustBeDocumented + |public annotation class Deprecated( + | val message: String, + | val replaceWith: ReplaceWith = ReplaceWith(""), + | val level: DeprecationLevel = DeprecationLevel.WARNING + |) + """.trimMargin(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val page = writerPlugin.writer.renderedContent("root/testpackage/-deprecated/index.html") + val sourceLink = page.getSourceLink() + + assertEquals( + "https://github.com/user/repo/tree/master/src/main/kotlin/basic/Deprecated.kt#L8", + sourceLink + ) + } + } + } + + @Test + fun `source link should be for actual typealias`() { + val mppConfiguration = dokkaConfiguration { + moduleName = "test" + sourceSets { + sourceSet { + name = "common" + sourceRoots = listOf("src/main/kotlin/common/Test.kt") + classpath = listOf(commonStdlibPath!!) + externalDocumentationLinks = listOf(stdlibExternalDocumentationLink) + } + sourceSet { + name = "jvm" + dependentSourceSets = setOf(DokkaSourceSetID("test", "common")) + sourceRoots = listOf("src/main/kotlin/jvm/Test.kt") + classpath = listOf(commonStdlibPath!!) + externalDocumentationLinks = listOf(stdlibExternalDocumentationLink) + sourceLinks = listOf( + SourceLinkDefinitionImpl( + localDirectory = "src/main/kotlin", + remoteUrl = URL("https://github.com/user/repo/tree/master/src/main/kotlin"), + remoteLineSuffix = "#L" + ) + ) + } + } + } + + val writerPlugin = TestOutputWriterPlugin() + + testInline( + """ + |/src/main/kotlin/common/Test.kt + |package example + | + |expect class Foo + | + |/src/main/kotlin/jvm/Test.kt + |package example + | + |class Bar + |actual typealias Foo = Bar + | + """.trimMargin(), + mppConfiguration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val page = writerPlugin.writer.renderedContent("test/example/-foo/index.html") + val sourceLink = page.getSourceLink() + + assertEquals( + "https://github.com/user/repo/tree/master/src/main/kotlin/jvm/Test.kt#L4", + sourceLink + ) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressTagFilterTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressTagFilterTest.kt new file mode 100644 index 00000000..5392a028 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressTagFilterTest.kt @@ -0,0 +1,211 @@ +/* + * 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 org.jetbrains.dokka.model.WithCompanion +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class SuppressTagFilterTest : BaseAbstractTest() { + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src") + } + } + } + + @Test + fun `should filter classes with suppress tag`() { + testInline( + """ + |/src/suppressed/NotSuppressed.kt + |/** + | * sample docs + |*/ + |class NotSuppressed + |/src/suppressed/Suppressed.kt + |/** + | * sample docs + | * @suppress + |*/ + |class Suppressed + """.trimIndent(), configuration + ) { + preMergeDocumentablesTransformationStage = { modules -> + assertEquals( + "NotSuppressed", + modules.flatMap { it.packages }.flatMap { it.classlikes }.singleOrNull()?.name + ) + } + } + } + + @Test + fun `should filter functions with suppress tag`() { + testInline( + """ + |/src/suppressed/Suppressed.kt + |class Suppressed { + | /** + | * sample docs + | * @suppress + | */ + | fun suppressedFun(){ } + |} + """.trimIndent(), configuration + ) { + preMergeDocumentablesTransformationStage = { modules -> + assertNull(modules.flatMap { it.packages }.flatMap { it.classlikes }.flatMap { it.functions } + .firstOrNull { it.name == "suppressedFun" }) + } + } + } + + @Test + fun `should filter top level functions`() { + testInline( + """ + |/src/suppressed/Suppressed.kt + |/** + | * sample docs + | * @suppress + | */ + |fun suppressedFun(){ } + | + |/** + | * Sample + | */ + |fun notSuppressedFun() { } + """.trimIndent(), configuration + ) { + preMergeDocumentablesTransformationStage = { modules -> + assertNull(modules.flatMap { it.packages }.flatMap { it.functions } + .firstOrNull { it.name == "suppressedFun" }) + } + } + } + + @Test + fun `should filter setter`() { + testInline( + """ + |/src/suppressed/Suppressed.kt + |var property: Int + |/** @suppress */ + |private set + """.trimIndent(), configuration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val prop = modules.flatMap { it.packages }.flatMap { it.properties } + .find { it.name == "property" } + assertNotNull(prop) + assertNotNull(prop.getter) + assertNull(prop.setter) + } + } + } + + @Test + fun `should filter top level type aliases`() { + testInline( + """ + |/src/suppressed/suppressed.kt + |/** + | * sample docs + | * @suppress + | */ + |typealias suppressedTypeAlias = String + | + |/** + | * Sample + | */ + |typealias notSuppressedTypeAlias = String + """.trimIndent(), configuration + ) { + preMergeDocumentablesTransformationStage = { modules -> + assertNull(modules.flatMap { it.packages }.flatMap { it.typealiases } + .firstOrNull { it.name == "suppressedTypeAlias" }) + assertNotNull(modules.flatMap { it.packages }.flatMap { it.typealiases } + .firstOrNull { it.name == "notSuppressedTypeAlias" }) + } + } + } + + @Test + fun `should filter companion object`() { + testInline( + """ + |/src/suppressed/Suppressed.kt + |class Suppressed { + |/** + | * @suppress + | */ + |companion object { + | val x = 1 + |} + |} + """.trimIndent(), configuration + ) { + preMergeDocumentablesTransformationStage = { modules -> + assertNull((modules.flatMap { it.packages }.flatMap { it.classlikes } + .firstOrNull { it.name == "Suppressed" } as? WithCompanion)?.companion) + } + } + } + + @Test + fun `should suppress inner classlike`() { + testInline( + """ + |/src/suppressed/Testing.kt + |class Testing { + | /** + | * Sample + | * @suppress + | */ + | inner class Suppressed { + | val x = 1 + | } + |} + """.trimIndent(), configuration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val testingClass = modules.flatMap { it.packages }.flatMap { it.classlikes }.single() + assertNull(testingClass.classlikes.firstOrNull()) + } + } + } + + @Test + fun `should suppress enum entry`() { + testInline( + """ + |/src/suppressed/Testing.kt + |enum class Testing { + | /** + | * Sample + | * @suppress + | */ + | SUPPRESSED, + | + | /** + | * Not suppressed + | */ + | NOT_SUPPRESSED + |} + """.trimIndent(), configuration + ) { + preMergeDocumentablesTransformationStage = { modules -> + val testingClass = modules.flatMap { it.packages }.flatMap { it.classlikes }.single() as DEnum + assertEquals(listOf("NOT_SUPPRESSED"), testingClass.entries.map { it.name }) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressedByConfigurationDocumentableFilterTransformerTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressedByConfigurationDocumentableFilterTransformerTest.kt new file mode 100644 index 00000000..f946a885 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/SuppressedByConfigurationDocumentableFilterTransformerTest.kt @@ -0,0 +1,193 @@ +/* + * 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.DokkaDefaults +import org.jetbrains.dokka.PackageOptionsImpl +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.links.DRI +import kotlin.test.Test +import kotlin.test.assertEquals + +class SuppressedByConfigurationDocumentableFilterTransformerTest : BaseAbstractTest() { + + @Test + fun `class filtered by package options`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src") + perPackageOptions = listOf( + packageOptions(matchingRegex = "suppressed.*", suppress = true), + packageOptions(matchingRegex = "default.*", suppress = false) + ) + } + } + } + + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + class Suppressed + + /src/default/Default.kt + package default + class Default.kt + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + assertEquals(1, module.children.size, "Expected just a single package in module") + assertEquals(1, module.packages.size, "Expected just a single package in module") + + val pkg = module.packages.single() + assertEquals("default", pkg.dri.packageName, "Expected 'default' package in module") + assertEquals(1, pkg.children.size, "Expected just a single child in 'default' package") + assertEquals(1, pkg.classlikes.size, "Expected just a single child in 'default' package") + + val classlike = pkg.classlikes.single() + assertEquals(DRI("default", "Default"), classlike.dri, "Expected 'Default' class in 'default' package") + } + } + } + + @Test + fun `class filtered by more specific package options`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src") + perPackageOptions = listOf( + packageOptions(matchingRegex = "parent.some.*", suppress = false), + packageOptions(matchingRegex = "parent.some.suppressed.*", suppress = true), + + packageOptions(matchingRegex = "parent.other.*", suppress = true), + packageOptions(matchingRegex = "parent.other.default.*", suppress = false) + ) + } + } + } + + testInline( + """ + /src/parent/some/Some.kt + package parent.some + class Some + + /src/parent/some/suppressed/Suppressed.kt + package parent.some.suppressed + class Suppressed + + /src/parent/other/Other.kt + package parent.other + class Other + + /src/parent/other/default/Default.kt + package parent.other.default + class Default + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + assertEquals(2, module.packages.size, "Expected two packages in module") + assertEquals( + listOf(DRI("parent.some"), DRI("parent.other.default")).sortedBy { it.packageName }, + module.packages.map { it.dri }.sortedBy { it.packageName }, + "Expected 'parent.some' and 'parent.other.default' packages to be not suppressed" + ) + } + } + } + + @Test + fun `class filtered by parent file path`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src") + suppressedFiles = listOf("src/suppressed") + } + } + } + + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + class Suppressed + + /src/default/Default.kt + package default + class Default.kt + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + assertEquals(1, module.children.size, "Expected just a single package in module") + assertEquals(1, module.packages.size, "Expected just a single package in module") + + val pkg = module.packages.single() + assertEquals("default", pkg.dri.packageName, "Expected 'default' package in module") + assertEquals(1, pkg.children.size, "Expected just a single child in 'default' package") + assertEquals(1, pkg.classlikes.size, "Expected just a single child in 'default' package") + + val classlike = pkg.classlikes.single() + assertEquals(DRI("default", "Default"), classlike.dri, "Expected 'Default' class in 'default' package") + } + } + } + + @Test + fun `class filtered by exact file path`() { + val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src") + suppressedFiles = listOf("src/suppressed/Suppressed.kt") + } + } + } + + testInline( + """ + /src/suppressed/Suppressed.kt + package suppressed + class Suppressed + + /src/default/Default.kt + package default + class Default.kt + """.trimIndent(), + configuration + ) { + documentablesMergingStage = { module -> + assertEquals(1, module.children.size, "Expected just a single package in module") + assertEquals(1, module.packages.size, "Expected just a single package in module") + + val pkg = module.packages.single() + assertEquals("default", pkg.dri.packageName, "Expected 'default' package in module") + assertEquals(1, pkg.children.size, "Expected just a single child in 'default' package") + assertEquals(1, pkg.classlikes.size, "Expected just a single child in 'default' package") + + val classlike = pkg.classlikes.single() + assertEquals(DRI("default", "Default"), classlike.dri, "Expected 'Default' class in 'default' package") + } + } + } + + private fun packageOptions( + matchingRegex: String, + suppress: Boolean + ) = PackageOptionsImpl( + matchingRegex = matchingRegex, + suppress = suppress, + includeNonPublic = true, + documentedVisibilities = DokkaDefaults.documentedVisibilities, + reportUndocumented = false, + skipDeprecated = false + ) + +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/transformers/isExceptionTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/isExceptionTest.kt new file mode 100644 index 00000000..a387c60d --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/transformers/isExceptionTest.kt @@ -0,0 +1,147 @@ +/* + * 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.transformers.documentables.isException +import org.jetbrains.dokka.model.DClass +import org.jetbrains.dokka.model.DTypeAlias +import utils.AbstractModelTest +import kotlin.test.Test + +class IsExceptionKotlinTest : AbstractModelTest("/src/main/kotlin/classes/Test.kt", "classes") { + @Test + fun `isException should work for kotlin exception`(){ + inlineModelTest( + """ + |class ExampleException(): Exception()""" + ) { + with((this / "classes" / "ExampleException").cast<DClass>()) { + name equals "ExampleException" + isException equals true + } + } + } + + @Test + fun `isException should work for java exceptions`(){ + inlineModelTest( + """ + |class ExampleException(): java.lang.Exception()""" + ) { + with((this / "classes" / "ExampleException").cast<DClass>()) { + name equals "ExampleException" + isException equals true + } + } + } + + @Test + fun `isException should work for RuntimeException`(){ + inlineModelTest( + """ + |class ExampleException(reason: String): RuntimeException(reason)""" + ) { + with((this / "classes" / "ExampleException").cast<DClass>()) { + name equals "ExampleException" + isException equals true + } + } + } + + @Test + fun `isException should work if exception is typealiased`(){ + inlineModelTest( + """ + |typealias ExampleException = java.lang.Exception""" + ) { + with((this / "classes" / "ExampleException").cast<DTypeAlias>()) { + name equals "ExampleException" + isException equals true + } + } + } + + @Test + fun `isException should work if exception is extending a typaliased class`(){ + inlineModelTest( + """ + |class ExampleException(): Exception() + |typealias ExampleExceptionAlias = ExampleException""" + ) { + with((this / "classes" / "ExampleExceptionAlias").cast<DTypeAlias>()) { + name equals "ExampleExceptionAlias" + isException equals true + } + } + } + + @Test + fun `isException should return false for a basic class`(){ + inlineModelTest( + """ + |class NotAnException(): Serializable""" + ) { + with((this / "classes" / "NotAnException").cast<DClass>()) { + name equals "NotAnException" + isException equals false + } + } + } + + @Test + fun `isException should return false for a typealias`(){ + inlineModelTest( + """ + |typealias NotAnException = Serializable""" + ) { + with((this / "classes" / "NotAnException").cast<DTypeAlias>()) { + name equals "NotAnException" + isException equals false + } + } + } +} + +class IsExceptionJavaTest: AbstractModelTest("/src/main/kotlin/java/Test.java", "java") { + @Test + fun `isException should work for java exceptions`(){ + inlineModelTest( + """ + |public class ExampleException extends java.lang.Exception { }""" + ) { + with((this / "java" / "ExampleException").cast<DClass>()) { + name equals "ExampleException" + isException equals true + } + } + } + + @Test + fun `isException should work for RuntimeException`(){ + inlineModelTest( + """ + |public class ExampleException extends java.lang.RuntimeException""" + ) { + with((this / "java" / "ExampleException").cast<DClass>()) { + name equals "ExampleException" + isException equals true + } + } + } + + @Test + fun `isException should return false for a basic class`(){ + inlineModelTest( + """ + |public class NotAnException extends Serializable""" + ) { + with((this / "java" / "NotAnException").cast<DClass>()) { + name equals "NotAnException" + isException equals false + } + } + } +} + |