/* * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package enums import matchers.content.* import org.jetbrains.dokka.SourceLinkDefinitionImpl import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest import org.jetbrains.dokka.model.DEnum import org.jetbrains.dokka.model.dfs import org.jetbrains.dokka.pages.ClasslikePage import org.jetbrains.dokka.pages.ClasslikePageNode import org.jetbrains.dokka.pages.ContentGroup import org.jsoup.Jsoup import org.jsoup.nodes.Element import signatures.renderedContent import utils.* import java.net.URL import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue class KotlinEnumsTest : BaseAbstractTest() { @Test fun `should preserve enum source ordering for documentables`() { val configuration = dokkaConfiguration { sourceSets { sourceSet { sourceRoots = listOf("src/") } } } val writerPlugin = TestOutputWriterPlugin() testInline( """ |/src/main/kotlin/basic/Test.kt |package testpackage | |enum class TestEnum { | ZERO, | ONE, | TWO, | THREE, | FOUR, | FIVE, | SIX, | SEVEN, | EIGHT, | NINE |} """.trimMargin(), configuration, pluginOverrides = listOf(writerPlugin) ) { documentablesTransformationStage = { module -> val testPackage = module.packages[0] assertEquals("testpackage", testPackage.name) val testEnum = testPackage.classlikes[0] as DEnum assertEquals("TestEnum", testEnum.name) val enumEntries = testEnum.entries assertEquals(10, enumEntries.count()) assertEquals("ZERO", enumEntries[0].name) assertEquals("ONE", enumEntries[1].name) assertEquals("TWO", enumEntries[2].name) assertEquals("THREE", enumEntries[3].name) assertEquals("FOUR", enumEntries[4].name) assertEquals("FIVE", enumEntries[5].name) assertEquals("SIX", enumEntries[6].name) assertEquals("SEVEN", enumEntries[7].name) assertEquals("EIGHT", enumEntries[8].name) assertEquals("NINE", enumEntries[9].name) } } } @Test fun `should preserve enum source ordering for generated pages`() { val configuration = dokkaConfiguration { sourceSets { sourceSet { sourceRoots = listOf("src/") } } } val writerPlugin = TestOutputWriterPlugin() testInline( """ |/src/main/kotlin/basic/Test.kt |package testpackage | |enum class TestEnum { | ZERO, | ONE, | TWO, | THREE, | FOUR, | FIVE, | SIX, | SEVEN, | EIGHT, | NINE |} """.trimMargin(), configuration, pluginOverrides = listOf(writerPlugin) ) { pagesGenerationStage = { rootPage -> val packagePage = rootPage.children[0] assertEquals("testpackage", packagePage.name) val testEnumNode = packagePage.children[0] assertEquals("TestEnum", testEnumNode.name) val enumEntries = testEnumNode.children.filterIsInstance() assertEquals(10, enumEntries.size) assertEquals("ZERO", enumEntries[0].name) assertEquals("ONE", enumEntries[1].name) assertEquals("TWO", enumEntries[2].name) assertEquals("THREE", enumEntries[3].name) assertEquals("FOUR", enumEntries[4].name) assertEquals("FIVE", enumEntries[5].name) assertEquals("SIX", enumEntries[6].name) assertEquals("SEVEN", enumEntries[7].name) assertEquals("EIGHT", enumEntries[8].name) assertEquals("NINE", enumEntries[9].name) } } } @Test fun `should preserve enum source ordering for rendered entries`() { val configuration = dokkaConfiguration { sourceSets { sourceSet { sourceRoots = listOf("src/") } } } val writerPlugin = TestOutputWriterPlugin() testInline( """ |/src/main/kotlin/basic/Test.kt |package testpackage | |enum class TestEnum { | ZERO, | ONE, | TWO, | THREE, | FOUR, | FIVE, | SIX, | SEVEN, | EIGHT, | NINE |} """.trimMargin(), configuration, pluginOverrides = listOf(writerPlugin) ) { renderingStage = { _, _ -> val enumEntriesOnPage = writerPlugin.writer.renderedContent("root/testpackage/-test-enum/index.html") .select("div[data-togglable=ENTRY] .table") .select("div.table-row") .select("div.keyValue") .select("div.title") .select("a") val enumEntries = enumEntriesOnPage.map { it.text() } assertEquals(10, enumEntries.size) assertEquals("ZERO", enumEntries[0]) assertEquals("ONE", enumEntries[1]) assertEquals("TWO", enumEntries[2]) assertEquals("THREE", enumEntries[3]) assertEquals("FOUR", enumEntries[4]) assertEquals("FIVE", enumEntries[5]) assertEquals("SIX", enumEntries[6]) assertEquals("SEVEN", enumEntries[7]) assertEquals("EIGHT", enumEntries[8]) assertEquals("NINE", enumEntries[9]) } } } @Test fun `should preserve enum source ordering for navigation menu`() { val configuration = dokkaConfiguration { sourceSets { sourceSet { sourceRoots = listOf("src/") } } } val writerPlugin = TestOutputWriterPlugin() testInline( """ |/src/main/kotlin/basic/Test.kt |package testpackage | |enum class TestEnum { | ZERO, | ONE, | TWO, | THREE, | FOUR, | FIVE, | SIX, | SEVEN, | EIGHT, | NINE |} """.trimMargin(), configuration, pluginOverrides = listOf(writerPlugin) ) { renderingStage = { _, _ -> val sideMenu = writerPlugin.writer.navigationHtml().select("div.sideMenuPart") assertEquals("ZERO", sideMenu.select("#root-nav-submenu-0-0-0").text()) assertEquals("ONE", sideMenu.select("#root-nav-submenu-0-0-1").text()) assertEquals("TWO", sideMenu.select("#root-nav-submenu-0-0-2").text()) assertEquals("THREE", sideMenu.select("#root-nav-submenu-0-0-3").text()) assertEquals("FOUR", sideMenu.select("#root-nav-submenu-0-0-4").text()) assertEquals("FIVE", sideMenu.select("#root-nav-submenu-0-0-5").text()) assertEquals("SIX", sideMenu.select("#root-nav-submenu-0-0-6").text()) assertEquals("SEVEN", sideMenu.select("#root-nav-submenu-0-0-7").text()) assertEquals("EIGHT", sideMenu.select("#root-nav-submenu-0-0-8").text()) assertEquals("NINE", sideMenu.select("#root-nav-submenu-0-0-9").text()) } } } fun TestOutputWriter.navigationHtml(): Element = contents.getValue("navigation.html").let { Jsoup.parse(it) } @Test fun `should handle companion object within enum`() { val configuration = dokkaConfiguration { sourceSets { sourceSet { sourceRoots = listOf("src/") } } } testInline( """ |/src/main/kotlin/basic/Test.kt |package testpackage | |enum class TestEnum { | E1, | E2; | companion object {} |} """.trimMargin(), configuration ) { documentablesTransformationStage = { m -> m.packages.let { p -> assertTrue(p.isNotEmpty(), "Package list cannot be empty") p.first().classlikes.let { c -> assertTrue(c.isNotEmpty(), "Classlikes list cannot be empty") val enum = c.first() as DEnum assertNotNull(enum.companion) } } } } } @Test fun enumWithMethods() { val configuration = dokkaConfiguration { sourceSets { sourceSet { sourceRoots = listOf("src/") classpath = listOfNotNull(jvmStdlibPath) } } } testInline( """ |/src/main/kotlin/basic/TestEnum.kt |package testpackage | | |interface Sample { | fun toBeImplemented(): String |} | |enum class TestEnum: Sample { | E1 { | override fun toBeImplemented(): String = "e1" | } |} """.trimMargin(), configuration ) { documentablesTransformationStage = { m -> m.packages.let { p -> p.first().classlikes.let { c -> val enum = c.first { it is DEnum } as DEnum val first = enum.entries.first() assertNotNull(first.functions.find { it.name == "toBeImplemented" }) } } } } } @Test @OnlyDescriptors("K2 has `compareTo`, that should be suppressed, due to #3196") fun `enum should have functions on page`() { val configuration = dokkaConfiguration { sourceSets { sourceSet { sourceRoots = listOf("src/") } } } testInline( """ |/src/main/kotlin/basic/TestEnum.kt |package testpackage | | |interface Sample { | fun toBeImplemented(): String |} | |enum class TestEnum: Sample { | E1 { | override fun toBeImplemented(): String = "e1" | } |} """.trimMargin(), configuration ) { pagesTransformationStage = { root -> root.contentPage("E1") { assertHasFunctions("toBeImplemented") } root.contentPage("TestEnum") { assertHasFunctions("toBeImplemented", "valueOf", "values") } } } } @Test fun enumWithAnnotationsOnEntries() { val configuration = dokkaConfiguration { sourceSets { sourceSet { sourceRoots = listOf("src/") } } } testInline( """ |/src/main/kotlin/basic/TestEnum.kt |package testpackage | |enum class TestEnum { | /** | Sample docs for E1 | **/ | @SinceKotlin("1.3") // This annotation is transparent due to lack of @MustBeDocumented annotation | E1 |} """.trimMargin(), configuration ) { pagesTransformationStage = { m -> val entryNode = m.children.first { it.name == "testpackage" }.children.first { it.name == "TestEnum" }.children.filterIsInstance().first() val signature = (entryNode.content as ContentGroup).dfs { it is ContentGroup && it.dci.toString() == "[testpackage/TestEnum.E1///PointingToDeclaration/{\"org.jetbrains.dokka.links.EnumEntryDRIExtra\":{\"key\":\"org.jetbrains.dokka.links.EnumEntryDRIExtra\"}}][Cover]" } as ContentGroup signature.assertNode { header(1) { +"E1" } platformHinted { group { group { link { +"E1" } } } group { group { group { +"Sample docs for E1" } } } } } } } } // Shouldn't try to give source links to synthetic methods (values, valueOf) if any are present // Initially reported for Java, making sure it doesn't fail for Kotlin either // https://github.com/Kotlin/dokka/issues/2544 @Test fun `kotlin enum with configured source links should not fail the build due to synthetic methods`() { 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/KotlinEnum.kt |package testpackage | |/** |* Doc |*/ |enum class KotlinEnum { | ONE, TWO, THREE |} """.trimMargin(), configuration, pluginOverrides = listOf(writerPlugin) ) { renderingStage = { _, _ -> val sourceLink = writerPlugin.writer.renderedContent("root/testpackage/-kotlin-enum/index.html") .select(".symbol .floating-right") .select("a[href]") .attr("href") assertEquals( "https://github.com/user/repo/tree/master/src/main/kotlin/basic/KotlinEnum.kt#L6", sourceLink ) } } } }