diff options
Diffstat (limited to 'dokka-subprojects/plugin-base/src/test/kotlin/renderers')
18 files changed, 2205 insertions, 0 deletions
diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/BasicTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/BasicTest.kt new file mode 100644 index 00000000..9653b7bb --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/BasicTest.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jetbrains.dokka.links.DRI +import renderers.testPage +import utils.Span +import utils.match +import kotlin.test.Test + +class BasicTest : HtmlRenderingOnlyTestBase() { + @Test + fun `unresolved DRI link should render as text`() { + val page = testPage { + link("linkText", DRI("nonexistentPackage", "nonexistentClass")) + } + + HtmlRenderer(context).render(page) + renderedContent.match(Span("linkText")) + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/BreadcrumbsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/BreadcrumbsTest.kt new file mode 100644 index 00000000..4bb0d41f --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/BreadcrumbsTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jsoup.nodes.Element +import signatures.renderedContent +import utils.* +import kotlin.test.Test + +class BreadcrumbsTest : BaseAbstractTest() { + + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + + @Test + fun `should add breadcrumbs with current element`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/kotlin/basic/TestClass.kt + |package testpackage + | + |class TestClass { + | fun foo() {} + |} + """.trimMargin(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/testpackage/-test-class/foo.html").selectBreadcrumbs().match( + link("root"), + delimiter(), + link("testpackage"), + delimiter(), + link("TestClass"), + delimiter(), + current("foo"), + ignoreSpanWithTokenStyle = true + ) + } + } + } + + @Test + fun `should mark only one element as current even if more elements have the same name`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/kotlin/basic/TestClass.kt + |package testpackage + | + |class testname { + | val testname: String = "" + |} + """.trimMargin(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + writerPlugin.writer.renderedContent("root/testpackage/testname/testname.html").selectBreadcrumbs().match( + link("root"), + delimiter(), + link("testpackage"), + delimiter(), + link("testname"), + delimiter(), + current("testname"), + ignoreSpanWithTokenStyle = true + ) + } + } + } + + private fun Element.selectBreadcrumbs() = this.select("div.breadcrumbs").single() + + private fun link(text: String): Tag = A(text) + private fun delimiter(): Tag = Span().withClasses("delimiter") + private fun current(text: String): Tag = Span(text).withClasses("current") +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/CoverPageTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/CoverPageTest.kt new file mode 100644 index 00000000..6b3ce2eb --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/CoverPageTest.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import signatures.renderedContent +import utils.TestOutputWriterPlugin +import kotlin.test.Test +import kotlin.test.assertEquals + +class CoverPageTest : BaseAbstractTest() { + + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOf(commonStdlibPath!!) + externalDocumentationLinks = listOf(stdlibExternalDocumentationLink) + } + } + } + + @Test + fun `names of nested inheritors`() { + val source = """ + |/src/main/kotlin/test/Test.kt + |package example + | + | sealed class Result{ + | class Success(): Result() + | class Failed(): Result() + | } + """ + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.writer.renderedContent("root/example/-result/index.html") + val tableInheritors = content.select("div.table").single { it.previousElementSibling()?.text() == "Inheritors" && it.childrenSize() == 2 } + assertEquals(tableInheritors.getElementsContainingOwnText("Failed").singleOrNull()?.tagName(), "a") + assertEquals(tableInheritors.getElementsContainingOwnText("Success").singleOrNull()?.tagName(), "a") + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/CustomFooterTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/CustomFooterTest.kt new file mode 100644 index 00000000..ff562c38 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/CustomFooterTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.DokkaConfigurationImpl +import org.jetbrains.dokka.PluginConfigurationImpl +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.DokkaBaseConfiguration +import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jetbrains.dokka.base.templating.toJsonString +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import renderers.testPage +import utils.A +import utils.Div +import utils.Span +import utils.match +import kotlin.test.Test + +class CustomFooterTest : HtmlRenderingOnlyTestBase() { + @Test + fun `should include message from custom footer`() { + val page = testPage { } + HtmlRenderer(context).render(page) + renderedContent.match( + Span(A()), + Span(Div("Custom message")), + Span(Span("Generated by "), A(Span("dokka"), Span())) + ) + } + + override val configuration: DokkaConfigurationImpl + get() = super.configuration.copy( + pluginsConfiguration = listOf( + PluginConfigurationImpl( + DokkaBase::class.java.canonicalName, + DokkaConfiguration.SerializationFormat.JSON, + toJsonString(DokkaBaseConfiguration(footerMessage = """<div style="color: red">Custom message</div>""")) + ) + ) + ) + + override val renderedContent: Element + get() = files.contents.getValue("test-page.html").let { Jsoup.parse(it) }.select(".footer").single() +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/DivergentTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/DivergentTest.kt new file mode 100644 index 00000000..ccc43f12 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/DivergentTest.kt @@ -0,0 +1,316 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.pages.ContentDivergentGroup +import renderers.testPage +import utils.Br +import utils.match +import kotlin.test.Test +import kotlin.test.assertEquals + +class DivergentTest : HtmlRenderingOnlyTestBase() { + + @Test + fun simpleWrappingCase() { + val page = testPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(js)) { + divergent { + text("a") + } + } + } + } + HtmlRenderer(context).render(page) + renderedContent.select("[data-togglable=DEFAULT/js]").single().match("a") + } + + @Test + fun noPlatformHintCase() { + val page = testPage { + divergentGroup(ContentDivergentGroup.GroupID("test"), implicitlySourceSetHinted = false) { + instance(setOf(DRI("test", "Test")), setOf(js)) { + divergent { + text("a") + } + } + } + } + HtmlRenderer(context).render(page) + renderedContent.match("a") + } + + @Test + fun divergentBetweenSourceSets() { + val page = testPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(js)) { + divergent { + text("a") + } + } + instance(setOf(DRI("test", "Test")), setOf(jvm)) { + divergent { + text("b") + } + } + instance(setOf(DRI("test", "Test")), setOf(native)) { + divergent { + text("c") + } + } + } + } + + HtmlRenderer(context).render(page) + val content = renderedContent + content.select("[data-togglable=DEFAULT/js]").single().match("a") + content.select("[data-togglable=DEFAULT/jvm]").single().match("b") + content.select("[data-togglable=DEFAULT/native]").single().match("c") + } + + @Test + fun divergentInOneSourceSet() { + val page = testPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(js)) { + divergent { + text("a") + } + } + instance(setOf(DRI("test", "Test2")), setOf(js)) { + divergent { + text("b") + } + } + instance(setOf(DRI("test", "Test3")), setOf(js)) { + divergent { + text("c") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.select("[data-togglable=DEFAULT/js]").single().match("abc") + } + + @Test + fun divergentInAndBetweenSourceSets() { + val page = testPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(native)) { + divergent { + text("a") + } + } + instance(setOf(DRI("test", "Test")), setOf(js)) { + divergent { + text("b") + } + } + instance(setOf(DRI("test", "Test")), setOf(jvm)) { + divergent { + text("c") + } + } + instance(setOf(DRI("test", "Test2")), setOf(js)) { + divergent { + text("d") + } + } + instance(setOf(DRI("test", "Test3")), setOf(native)) { + divergent { + text("e") + } + } + } + } + + HtmlRenderer(context).render(page) + val content = renderedContent + val orderOfTabs = content.select(".platform-bookmarks-row").single().children().map { it.attr("data-toggle") } + + assertEquals(listOf("DEFAULT/js", "DEFAULT/jvm", "DEFAULT/native"), orderOfTabs) + + content.select("[data-togglable=DEFAULT/native]").single().match("ae") + content.select("[data-togglable=DEFAULT/js]").single().match("bd") + content.select("[data-togglable=DEFAULT/jvm]").single().match("c") + } + + @Test + fun divergentInAndBetweenSourceSetsWithGrouping() { + val page = testPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(native)) { + divergent { + text("a") + } + after { + text("a+") + } + } + instance(setOf(DRI("test", "Test")), setOf(js)) { + divergent { + text("b") + } + after { + text("bd+") + } + } + instance(setOf(DRI("test", "Test")), setOf(jvm)) { + divergent { + text("c") + } + } + instance(setOf(DRI("test", "Test2")), setOf(js)) { + divergent { + text("d") + } + after { + text("bd+") + } + } + instance(setOf(DRI("test", "Test3")), setOf(native)) { + divergent { + text("e") + } + after { + text("e+") + } + } + } + } + + HtmlRenderer(context).render(page) + val content = renderedContent + content.select("[data-togglable=DEFAULT/native]").single().match("aa+", Br, "ee+") + content.select("[data-togglable=DEFAULT/js]").single().match("bdbd+") + content.select("[data-togglable=DEFAULT/jvm]").single().match("c") + } + + @Test + fun divergentSameBefore() { + val page = testPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(native)) { + before { + text("ab-") + } + divergent { + text("a") + } + } + instance(setOf(DRI("test", "Test2")), setOf(native)) { + before { + text("ab-") + } + divergent { + text("b") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.select("[data-togglable=DEFAULT/native]").single().match("ab-ab") + } + + @Test + fun divergentSameAfter() { + val page = testPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(native)) { + divergent { + text("a") + } + after { + text("ab+") + } + } + instance(setOf(DRI("test", "Test2")), setOf(native)) { + divergent { + text("b") + } + after { + text("ab+") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.select("[data-togglable=DEFAULT/native]").single().match("abab+") + } + + @Test + fun divergentGroupedByBeforeAndAfter() { + val page = testPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(native)) { + before { + text("ab-") + } + divergent { + text("a") + } + after { + text("ab+") + } + } + instance(setOf(DRI("test", "Test2")), setOf(native)) { + before { + text("ab-") + } + divergent { + text("b") + } + after { + text("ab+") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.select("[data-togglable=DEFAULT/native]").single().match("ab-abab+") + } + + @Test + fun divergentDifferentBeforeAndAfter() { + val page = testPage { + divergentGroup(ContentDivergentGroup.GroupID("test")) { + instance(setOf(DRI("test", "Test")), setOf(native)) { + before { + text("a-") + } + divergent { + text("a") + } + after { + text("ab+") + } + } + instance(setOf(DRI("test", "Test2")), setOf(native)) { + before { + text("b-") + } + divergent { + text("b") + } + after { + text("ab+") + } + } + } + } + + HtmlRenderer(context).render(page) + renderedContent.select("[data-togglable=DEFAULT/native]").single().match("a-aab+", Br, "b-bab+") + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/FooterMessageTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/FooterMessageTest.kt new file mode 100644 index 00000000..149f970c --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/FooterMessageTest.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.DokkaBaseConfiguration.Companion.defaultFooterMessage +import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import renderers.testPage +import utils.A +import utils.Span +import utils.match +import kotlin.test.Test + +class FooterMessageTest : HtmlRenderingOnlyTestBase() { + @Test + fun `should include defaultFooter`() { + val page = testPage { } + HtmlRenderer(context).render(page) + renderedContent.match( + Span(A()), + Span(defaultFooterMessage), + Span(Span("Generated by "), A(Span("dokka"), Span())) + ) + } + + override val renderedContent: Element + get() = files.contents.getValue("test-page.html").let { Jsoup.parse(it) }.select(".footer").single() +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/FormattingUtilsTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/FormattingUtilsTest.kt new file mode 100644 index 00000000..028ffa77 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/FormattingUtilsTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import kotlinx.html.body +import kotlinx.html.html +import kotlinx.html.stream.createHTML +import org.jetbrains.dokka.base.renderers.html.buildBreakableText +import kotlin.test.Test +import kotlin.test.assertEquals + +class FormattingUtilsTest { + @Test + fun `should build breakable text`(){ + val testedText = "kotlinx.collections.immutable" + val expectedHtml = """ + <html> + <body><span>kotlinx.</span><wbr></wbr><span>collections.</span><wbr></wbr><span>immutable</span></body> + </html> + """.trimIndent() + + val html = createHTML(prettyPrint = true).html { + body { + buildBreakableText(testedText) + } + } + + assertEquals(expectedHtml.trim(), html.trim()) + } + + @Test + fun `should build breakable text without empty spans`(){ + val testedText = "Package org.jetbrains.dokka.it.moduleC" + val expectedHtml = """ + <html> + <body><span><span>Package</span></span> <span>org.</span><wbr></wbr><span>jetbrains.</span><wbr></wbr><span>dokka.</span><wbr></wbr><span>it.</span><wbr></wbr><span>moduleC</span></body> + </html> + """.trimIndent() + + val html = createHTML(prettyPrint = true).html { + body { + buildBreakableText(testedText) + } + } + + assertEquals(expectedHtml.trim(), html.trim()) + } + + @Test + fun `should build breakable text for text with braces`(){ + val testedText = "[Common]kotlinx.collections.immutable" + val expectedHtml = """ + <html> + <body><span>[Common]kotlinx.</span><wbr></wbr><span>collections.</span><wbr></wbr><span>immutable</span></body> + </html> + """.trimIndent() + + val html = createHTML(prettyPrint = true).html { + body { + buildBreakableText(testedText) + } + } + + assertEquals(expectedHtml.trim(), html.trim()) + } + + @Test + fun `should build breakable text for camel case notation`(){ + val testedText = "DokkkkkkkaIsTheBest" + val expectedHtml = """ + <html> + <body><span>Dokkkkkkka</span><wbr></wbr><span>Is</span><wbr></wbr><span>The</span><wbr></wbr><span><span>Best</span></span></body> + </html> + """.trimIndent() + + val html = createHTML(prettyPrint = true).html { + body { + buildBreakableText(testedText) + } + } + + assertEquals(expectedHtml.trim(), html.trim()) + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/GroupWrappingTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/GroupWrappingTest.kt new file mode 100644 index 00000000..cc9b763d --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/GroupWrappingTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jetbrains.dokka.pages.TextStyle +import renderers.testPage +import utils.Div +import utils.P +import utils.match +import kotlin.test.Test + +class GroupWrappingTest : HtmlRenderingOnlyTestBase() { + + @Test + fun notWrapped() { + val page = testPage { + group { + text("a") + text("b") + } + text("c") + } + + HtmlRenderer(context).render(page) + + renderedContent.match("abc") + } + + @Test + fun paragraphWrapped() { + val page = testPage { + group(styles = setOf(TextStyle.Paragraph)) { + text("a") + text("b") + } + text("c") + } + + HtmlRenderer(context).render(page) + + renderedContent.match(P("ab"), "c") + } + + @Test + fun blockWrapped() { + val page = testPage { + group(styles = setOf(TextStyle.Block)) { + text("a") + text("b") + } + text("c") + } + + HtmlRenderer(context).render(page) + + renderedContent.match(Div("ab"), "c") + } + + @Test + fun nested() { + val page = testPage { + group(styles = setOf(TextStyle.Block)) { + text("a") + group(styles = setOf(TextStyle.Block)) { + group(styles = setOf(TextStyle.Block)) { + text("b") + text("c") + } + } + text("d") + } + } + + HtmlRenderer(context).render(page) + + renderedContent.match(Div("a", Div(Div("bc")), "d")) + } + +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/HeaderTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/HeaderTest.kt new file mode 100644 index 00000000..c19f965f --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/HeaderTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.DokkaConfigurationImpl +import org.jetbrains.dokka.PluginConfigurationImpl +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.DokkaBaseConfiguration +import org.jetbrains.dokka.base.templating.toJsonString +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.pages.RootPageNode +import org.jetbrains.dokka.plugability.DokkaContext +import org.jsoup.Jsoup +import utils.TestOutputWriter +import utils.TestOutputWriterPlugin +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class HeaderTest : BaseAbstractTest() { + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + name = "jvm" + sourceRoots = listOf("src/jvm") + } + sourceSet { + name = "js" + sourceRoots = listOf("src/js") + } + } + } + + @Test + fun `should include homepage link if homepageLink is provided`() { + testRendering( + DokkaBaseConfiguration(homepageLink = "https://github.com/Kotlin/dokka/") + ) { _, _, writer -> + val renderedContent = navigationElement(writer) + + val sourceLinkElement = + assertNotNull(renderedContent.getElementById("homepage-link"), "Source link element not found") + val aElement = assertNotNull(sourceLinkElement.selectFirst("a")) + assertEquals("https://github.com/Kotlin/dokka/", aElement.attr("href")) + } + } + + @Test + fun `should not include homepage link by default`() { + testRendering(null) { _, _, writer -> + val renderedContent = navigationElement(writer) + assertNull(renderedContent.getElementById("homepage-link"), "Source link element found") + } + } + + private fun testRendering( + baseConfiguration: DokkaBaseConfiguration?, + block: (RootPageNode, DokkaContext, writer: TestOutputWriter) -> Unit + ) { + fun configuration(): DokkaConfigurationImpl { + baseConfiguration ?: return configuration + return configuration.copy( + pluginsConfiguration = listOf( + PluginConfigurationImpl( + DokkaBase::class.java.canonicalName, + DokkaConfiguration.SerializationFormat.JSON, + toJsonString(baseConfiguration) + ) + ) + ) + } + + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/jvm/Test.kt + |fun test() {} + |/src/js/Test.kt + |fun test() {} + """, + configuration(), + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { node, context -> + block(node, context, writerPlugin.writer) + } + } + } + + private fun navigationElement(writer: TestOutputWriter) = + writer + .contents + .getValue("index.html") + .let(Jsoup::parse) + .select(".navigation") + .single() + +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt new file mode 100644 index 00000000..4e098371 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/HtmlRenderingOnlyTestBase.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.DokkaConfigurationImpl +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.DokkaBase +import org.jetbrains.dokka.base.renderers.RootCreator +import org.jetbrains.dokka.base.resolvers.external.DefaultExternalLocationProviderFactory +import org.jetbrains.dokka.base.resolvers.external.javadoc.JavadocExternalLocationProviderFactory +import org.jetbrains.dokka.base.resolvers.local.DokkaLocationProviderFactory +import org.jetbrains.dokka.testApi.context.MockContext +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import renderers.RenderingOnlyTestBase +import testApi.testRunner.defaultSourceSet +import utils.TestOutputWriter +import java.io.File + +abstract class HtmlRenderingOnlyTestBase : RenderingOnlyTestBase<Element>() { + + protected val js = defaultSourceSet.copy( + "JS", + defaultSourceSet.sourceSetID.copy(sourceSetName = "js"), + analysisPlatform = Platform.js, + sourceRoots = setOf(File("pl1")) + ) + protected val jvm = defaultSourceSet.copy( + "JVM", + defaultSourceSet.sourceSetID.copy(sourceSetName = "jvm"), + + analysisPlatform = Platform.jvm, + sourceRoots = setOf(File("pl1")) + ) + protected val native = defaultSourceSet.copy( + "NATIVE", + defaultSourceSet.sourceSetID.copy(sourceSetName = "native"), + analysisPlatform = Platform.native, + sourceRoots = setOf(File("pl1")) + ) + + val files = TestOutputWriter() + + open val configuration = DokkaConfigurationImpl( + sourceSets = listOf(js, jvm, native), + finalizeCoroutines = false + ) + + override val context = MockContext( + DokkaBase().outputWriter to { files }, + DokkaBase().locationProviderFactory to ::DokkaLocationProviderFactory, + DokkaBase().htmlPreprocessors to { RootCreator }, + DokkaBase().externalLocationProviderFactory to ::JavadocExternalLocationProviderFactory, + DokkaBase().externalLocationProviderFactory to ::DefaultExternalLocationProviderFactory, + testConfiguration = configuration + ) + + override val renderedContent: Element by lazy { + files.contents.getValue("test-page.html").let { Jsoup.parse(it) }.select("#content").single() + } + + protected fun linesAfterContentTag() = + files.contents.getValue("test-page.html").lines() + .dropWhile { !it.contains("""<div id="content">""") } + .joinToString(separator = "") { it.trim() } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/ListStylesTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/ListStylesTest.kt new file mode 100644 index 00000000..f8afb54c --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/ListStylesTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jetbrains.dokka.pages.ListStyle +import renderers.testPage +import utils.Dd +import utils.Dl +import utils.Dt +import utils.match +import kotlin.test.Test + + +class ListStylesTest : HtmlRenderingOnlyTestBase() { + + @Test + fun `description list render`() { + val page = testPage { + descriptionList { + item(styles = setOf(ListStyle.DescriptionTerm)) { + text("Description term #1") + } + item(styles = setOf(ListStyle.DescriptionTerm)) { + text("Description term #2") + } + item(styles = setOf(ListStyle.DescriptionDetails)) { + text("Description details describing terms #1 and #2") + } + } + } + + + HtmlRenderer(context).render(page) + renderedContent.match( + Dl( + Dt("Description term #1"), + Dt("Description term #2"), + Dd("Description details describing terms #1 and #2") + ) + ) + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/NavigationIconTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/NavigationIconTest.kt new file mode 100644 index 00000000..d57f84df --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/NavigationIconTest.kt @@ -0,0 +1,292 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import utils.TestOutputWriterPlugin +import utils.navigationHtml +import utils.selectNavigationGrid +import kotlin.test.Test +import kotlin.test.assertEquals + +class NavigationIconTest : BaseAbstractTest() { + + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + + @Test + fun `should include all navigation icons`() { + val source = """ + |/src/main/kotlin/com/example/Empty.kt + |package com.example + | + |class Empty {} + """ + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val navIconAssets = writerPlugin.writer.contents + .filterKeys { it.startsWith("images/nav-icons") } + .keys.sorted() + + assertEquals(16, navIconAssets.size) + assertEquals("images/nav-icons/abstract-class-kotlin.svg", navIconAssets[0]) + assertEquals("images/nav-icons/abstract-class.svg", navIconAssets[1]) + assertEquals("images/nav-icons/annotation-kotlin.svg", navIconAssets[2]) + assertEquals("images/nav-icons/annotation.svg", navIconAssets[3]) + assertEquals("images/nav-icons/class-kotlin.svg", navIconAssets[4]) + assertEquals("images/nav-icons/class.svg", navIconAssets[5]) + assertEquals("images/nav-icons/enum-kotlin.svg", navIconAssets[6]) + assertEquals("images/nav-icons/enum.svg", navIconAssets[7]) + assertEquals("images/nav-icons/exception-class.svg", navIconAssets[8]) + assertEquals("images/nav-icons/field-value.svg", navIconAssets[9]) + assertEquals("images/nav-icons/field-variable.svg", navIconAssets[10]) + assertEquals("images/nav-icons/function.svg", navIconAssets[11]) + assertEquals("images/nav-icons/interface-kotlin.svg", navIconAssets[12]) + assertEquals("images/nav-icons/interface.svg", navIconAssets[13]) + assertEquals("images/nav-icons/object.svg", navIconAssets[14]) + assertEquals("images/nav-icons/typealias-kotlin.svg", navIconAssets[15]) + } + } + } + + @Test + fun `should add icon styles to kotlin class navigation item`() { + assertNavigationIcon( + source = kotlinSource("class Clazz {}"), + expectedIconClass = "class-kt", + expectedNavLinkText = "Clazz" + ) + } + + @Test + fun `should add icon styles to java class navigation item`() { + assertNavigationIcon( + source = javaSource( + className = "JavaClazz", + source = "public class JavaClazz {}" + ), + expectedIconClass = "class", + expectedNavLinkText = "JavaClazz" + ) + } + + @Test + fun `should add icon styles to kotlin abstract class navigation item`() { + assertNavigationIcon( + source = kotlinSource("abstract class AbstractClazz {}"), + expectedIconClass = "abstract-class-kt", + expectedNavLinkText = "AbstractClazz" + ) + } + + @Test + fun `should add icon styles to java abstract class navigation item`() { + assertNavigationIcon( + source = javaSource( + className = "AbstractJavaClazz", + source = "public abstract class AbstractJavaClazz {}" + ), + expectedIconClass = "abstract-class", + expectedNavLinkText = "AbstractJavaClazz" + ) + } + + @Test + fun `should add icon styles to kotlin typealias navigation item`() { + assertNavigationIcon( + source = kotlinSource("typealias KotlinTypealias = String"), + expectedIconClass = "typealias-kt", + expectedNavLinkText = "KotlinTypealias" + ) + } + + @Test + fun `should add icon styles to kotlin enum navigation item`() { + assertNavigationIcon( + source = kotlinSource("enum class KotlinEnum {}"), + expectedIconClass = "enum-class-kt", + expectedNavLinkText = "KotlinEnum" + ) + } + + @Test + fun `should add icon styles to java enum class navigation item`() { + assertNavigationIcon( + source = javaSource( + className = "JavaEnum", + source = "public enum JavaEnum {}" + ), + expectedIconClass = "enum-class", + expectedNavLinkText = "JavaEnum" + ) + } + + @Test + fun `should add icon styles to kotlin annotation navigation item`() { + assertNavigationIcon( + source = kotlinSource("annotation class KotlinAnnotation"), + expectedIconClass = "annotation-class-kt", + expectedNavLinkText = "KotlinAnnotation" + ) + } + + @Test + fun `should add icon styles to java annotation navigation item`() { + assertNavigationIcon( + source = javaSource( + className = "JavaAnnotation", + source = "public @interface JavaAnnotation {}" + ), + expectedIconClass = "annotation-class", + expectedNavLinkText = "JavaAnnotation" + ) + } + + + @Test + fun `should add icon styles to kotlin interface navigation item`() { + assertNavigationIcon( + source = kotlinSource("interface KotlinInterface"), + expectedIconClass = "interface-kt", + expectedNavLinkText = "KotlinInterface" + ) + } + + @Test + fun `should add icon styles to java interface navigation item`() { + assertNavigationIcon( + source = javaSource( + className = "JavaInterface", + source = "public interface JavaInterface {}" + ), + expectedIconClass = "interface", + expectedNavLinkText = "JavaInterface" + ) + } + + @Test + fun `should add icon styles to kotlin function navigation item`() { + assertNavigationIcon( + source = kotlinSource("fun ktFunction() {}"), + expectedIconClass = "function", + expectedNavLinkText = "ktFunction()" + ) + } + + @Test + fun `should add icon styles to kotlin exception class navigation item`() { + assertNavigationIcon( + source = kotlinSource("class KotlinException : Exception() {}"), + expectedIconClass = "exception-class", + expectedNavLinkText = "KotlinException" + ) + } + + @Test + fun `should add icon styles to kotlin object navigation item`() { + assertNavigationIcon( + source = kotlinSource("object KotlinObject {}"), + expectedIconClass = "object", + expectedNavLinkText = "KotlinObject" + ) + } + + @Test + fun `should add icon styles to kotlin val navigation item`() { + assertNavigationIcon( + source = kotlinSource("val value: String? = null"), + expectedIconClass = "val", + expectedNavLinkText = "value" + ) + } + + @Test + fun `should add icon styles to kotlin var navigation item`() { + assertNavigationIcon( + source = kotlinSource("var variable: String? = null"), + expectedIconClass = "var", + expectedNavLinkText = "variable" + ) + } + + private fun kotlinSource(source: String): String { + return """ + |/src/main/kotlin/com/example/Example.kt + |package com.example + | + |$source + """.trimIndent() + } + + private fun javaSource(className: String, source: String): String { + return """ + |/src/main/java/com/example/$className.java + |package com.example; + | + |$source + """.trimIndent() + } + + private fun assertNavigationIcon(source: String, expectedIconClass: String, expectedNavLinkText: String) { + val writerPlugin = TestOutputWriterPlugin() + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.writer.navigationHtml().select("div.sideMenuPart") + val navigationGrid = content.selectNavigationGrid() + + val classNames = navigationGrid.child(0).classNames().toList() + assertEquals("nav-link-child", classNames[0]) + assertEquals("nav-icon", classNames[1]) + assertEquals(expectedIconClass, classNames[2]) + + val navLinkText = navigationGrid.child(1).text() + assertEquals(expectedNavLinkText, navLinkText) + } + } + } + + @Test + fun `should not generate nav link grids or icons for packages and modules`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/kotlin/com/example/Example.kt + |package com.example + | + |class Example {} + """.trimIndent(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.writer.navigationHtml().select("div.sideMenuPart") + + assertEquals(3, content.size) + assertEquals("root-nav-submenu", content[0].id()) + assertEquals("root-nav-submenu-0", content[1].id()) + assertEquals("root-nav-submenu-0-0", content[2].id()) + + // there's 3 nav items, but only one icon + val navLinkGrids = content.select("span.nav-icon") + assertEquals(1, navLinkGrids.size) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/NavigationTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/NavigationTest.kt new file mode 100644 index 00000000..02074810 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/NavigationTest.kt @@ -0,0 +1,414 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.renderers.html.NavigationNodeIcon +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jsoup.nodes.Element +import utils.TestOutputWriterPlugin +import utils.navigationHtml +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class NavigationTest : BaseAbstractTest() { + + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + + @Test + fun `should sort alphabetically ignoring case`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/kotlin/com/example/Sequences.kt + |package com.example + | + |fun <T> sequence(): Sequence<T> + | + |fun <T> Sequence(): Sequence<T> + | + |fun <T> Sequence<T>.any() {} + | + |interface Sequence<T> + """.trimMargin(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.writer.navigationHtml().select("div.sideMenuPart") + assertEquals(6, content.size) + + // Navigation menu should be the following: + // - root + // - com.example + // - any() + // - Sequence interface + // - Sequence() + // - sequence() + + content[0].assertNavigationLink( + id = "root-nav-submenu", + text = "root", + address = "index.html", + ) + + content[1].assertNavigationLink( + id = "root-nav-submenu-0", + text = "com.example", + address = "root/com.example/index.html", + ) + + content[2].assertNavigationLink( + id = "root-nav-submenu-0-0", + text = "any()", + address = "root/com.example/any.html", + icon = NavigationNodeIcon.FUNCTION + ) + + content[3].assertNavigationLink( + id = "root-nav-submenu-0-1", + text = "Sequence", + address = "root/com.example/-sequence/index.html", + icon = NavigationNodeIcon.INTERFACE_KT + ) + + content[4].assertNavigationLink( + id = "root-nav-submenu-0-2", + text = "Sequence()", + address = "root/com.example/-sequence.html", + icon = NavigationNodeIcon.FUNCTION + ) + + content[5].assertNavigationLink( + id = "root-nav-submenu-0-3", + text = "sequence()", + address = "root/com.example/sequence.html", + icon = NavigationNodeIcon.FUNCTION + ) + } + } + } + + @Test + fun `should strike deprecated class link`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/kotlin/com/example/SimpleDeprecatedClass.kt + |package com.example + | + |@Deprecated("reason") + |class SimpleDeprecatedClass {} + """.trimIndent(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.writer.navigationHtml().select("div.sideMenuPart") + assertEquals(3, content.size) + + // Navigation menu should be the following: + // - root + // - com.example + // - SimpleDeprecatedClass + + content[0].assertNavigationLink( + id = "root-nav-submenu", + text = "root", + address = "index.html", + ) + + content[1].assertNavigationLink( + id = "root-nav-submenu-0", + text = "com.example", + address = "root/com.example/index.html", + ) + + content[2].assertNavigationLink( + id = "root-nav-submenu-0-0", + text = "SimpleDeprecatedClass", + address = "root/com.example/-simple-deprecated-class/index.html", + icon = NavigationNodeIcon.CLASS_KT, + isStrikethrough = true + ) + } + } + } + + @Test + fun `should not strike pages where only one of N documentables is deprecated`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/kotlin/com/example/File.kt + |package com.example + | + |/** + | * First + | */ + |@Deprecated("reason") + |fun functionWithCommonName() + | + |/** + | * Second + | */ + |fun functionWithCommonName() + """.trimIndent(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.writer.navigationHtml().select("div.sideMenuPart") + assertEquals(3, content.size) + + // Navigation menu should be the following: + // - root + // - com.example + // - functionWithCommonName + + content[0].assertNavigationLink( + id = "root-nav-submenu", + text = "root", + address = "index.html", + ) + + content[1].assertNavigationLink( + id = "root-nav-submenu-0", + text = "com.example", + address = "root/com.example/index.html", + ) + + content[2].assertNavigationLink( + id = "root-nav-submenu-0-0", + text = "functionWithCommonName()", + address = "root/com.example/function-with-common-name.html", + icon = NavigationNodeIcon.FUNCTION, + isStrikethrough = false + ) + } + } + } + + @Test + fun `should have expandable classlikes`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/kotlin/com/example/WithInner.kt + |package com.example + | + |class WithInner { + | // in-class functions should not be in navigation + | fun a() {} + | fun b() {} + | fun c() {} + | + | class InnerClass {} + | interface InnerInterface {} + | enum class InnerEnum {} + | object InnerObject {} + | annotation class InnerAnnotation {} + | companion object CompanionObject {} + |} + """.trimIndent(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.writer.navigationHtml().select("div.sideMenuPart") + assertEquals(9, content.size) + + // Navigation menu should be the following, sorted by name: + // - root + // - com.example + // - WithInner + // - CompanionObject + // - InnerAnnotation + // - InnerClass + // - InnerEnum + // - InnerInterface + // - InnerObject + + content[0].assertNavigationLink( + id = "root-nav-submenu", + text = "root", + address = "index.html", + ) + + content[1].assertNavigationLink( + id = "root-nav-submenu-0", + text = "com.example", + address = "root/com.example/index.html", + ) + + content[2].assertNavigationLink( + id = "root-nav-submenu-0-0", + text = "WithInner", + address = "root/com.example/-with-inner/index.html", + icon = NavigationNodeIcon.CLASS_KT + ) + + content[3].assertNavigationLink( + id = "root-nav-submenu-0-0-0", + text = "CompanionObject", + address = "root/com.example/-with-inner/-companion-object/index.html", + icon = NavigationNodeIcon.OBJECT + ) + + content[4].assertNavigationLink( + id = "root-nav-submenu-0-0-1", + text = "InnerAnnotation", + address = "root/com.example/-with-inner/-inner-annotation/index.html", + icon = NavigationNodeIcon.ANNOTATION_CLASS_KT + ) + + content[5].assertNavigationLink( + id = "root-nav-submenu-0-0-2", + text = "InnerClass", + address = "root/com.example/-with-inner/-inner-class/index.html", + icon = NavigationNodeIcon.CLASS_KT + ) + + content[6].assertNavigationLink( + id = "root-nav-submenu-0-0-3", + text = "InnerEnum", + address = "root/com.example/-with-inner/-inner-enum/index.html", + icon = NavigationNodeIcon.ENUM_CLASS_KT + ) + + content[7].assertNavigationLink( + id = "root-nav-submenu-0-0-4", + text = "InnerInterface", + address = "root/com.example/-with-inner/-inner-interface/index.html", + icon = NavigationNodeIcon.INTERFACE_KT + ) + + content[8].assertNavigationLink( + id = "root-nav-submenu-0-0-5", + text = "InnerObject", + address = "root/com.example/-with-inner/-inner-object/index.html", + icon = NavigationNodeIcon.OBJECT + ) + } + } + } + + @Test + fun `should be able to have deeply nested classlikes`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/kotlin/com/example/DeeplyNested.kt + |package com.example + | + |class DeeplyNested { + | class FirstLevelClass { + | interface SecondLevelInterface { + | object ThirdLevelObject { + | annotation class FourthLevelAnnotation {} + | } + | } + | } + |} + """.trimIndent(), + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val content = writerPlugin.writer.navigationHtml().select("div.sideMenuPart") + assertEquals(7, content.size) + + // Navigation menu should be the following + // - root + // - com.example + // - DeeplyNested + // - FirstLevelClass + // - SecondLevelInterface + // - ThirdLevelObject + // - FourthLevelAnnotation + + content[0].assertNavigationLink( + id = "root-nav-submenu", + text = "root", + address = "index.html", + ) + + content[1].assertNavigationLink( + id = "root-nav-submenu-0", + text = "com.example", + address = "root/com.example/index.html", + ) + + content[2].assertNavigationLink( + id = "root-nav-submenu-0-0", + text = "DeeplyNested", + address = "root/com.example/-deeply-nested/index.html", + icon = NavigationNodeIcon.CLASS_KT + ) + + content[3].assertNavigationLink( + id = "root-nav-submenu-0-0-0", + text = "FirstLevelClass", + address = "root/com.example/-deeply-nested/-first-level-class/index.html", + icon = NavigationNodeIcon.CLASS_KT + ) + + content[4].assertNavigationLink( + id = "root-nav-submenu-0-0-0-0", + text = "SecondLevelInterface", + address = "root/com.example/-deeply-nested/-first-level-class/-second-level-interface/index.html", + icon = NavigationNodeIcon.INTERFACE_KT + ) + + content[5].assertNavigationLink( + id = "root-nav-submenu-0-0-0-0-0", + text = "ThirdLevelObject", + address = "root/com.example/-deeply-nested/-first-level-class/-second-level-interface/" + + "-third-level-object/index.html", + icon = NavigationNodeIcon.OBJECT + ) + + content[6].assertNavigationLink( + id = "root-nav-submenu-0-0-0-0-0-0", + text = "FourthLevelAnnotation", + address = "root/com.example/-deeply-nested/-first-level-class/-second-level-interface/" + + "-third-level-object/-fourth-level-annotation/index.html", + icon = NavigationNodeIcon.ANNOTATION_CLASS_KT + ) + } + } + } + + private fun Element.assertNavigationLink( + id: String, text: String, address: String, icon: NavigationNodeIcon? = null, isStrikethrough: Boolean = false + ) { + assertEquals(id, this.id()) + + val link = this.selectFirst("a") + assertNotNull(link) + assertEquals(text, link.text()) + assertEquals(address, link.attr("href")) + if (icon != null) { + val iconStyles = + this.selectFirst("div.overview span.nav-link-grid")?.child(0)?.classNames()?.toList() ?: emptyList() + assertEquals(3, iconStyles.size) + assertEquals("nav-link-child", iconStyles[0]) + assertEquals(icon.style(), "${iconStyles[1]} ${iconStyles[2]}") + } + if (isStrikethrough) { + val textInsideStrikethrough = link.selectFirst("strike")?.text() + assertEquals(text, textInsideStrikethrough) + } else { + assertNull(link.selectFirst("strike")) + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/SearchbarDataInstallerTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/SearchbarDataInstallerTest.kt new file mode 100644 index 00000000..a5f5feb5 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/SearchbarDataInstallerTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import utils.TestOutputWriterPlugin +import utils.pagesJson +import kotlin.test.Test +import kotlin.test.assertEquals + +class SearchbarDataInstallerTest: BaseAbstractTest() { + + @Test // see #2289 + fun `should display description of root declarations without a leading dot`() { + val configuration = dokkaConfiguration { + moduleName = "Dokka Module" + + sourceSets { + sourceSet { + sourceRoots = listOf("src/kotlin/Test.kt") + } + } + } + + val source = """ + |/src/kotlin/Test.kt + | + |class Test + | + """.trimIndent() + + val writerPlugin = TestOutputWriterPlugin() + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val searchRecords = writerPlugin.writer.pagesJson() + + assertEquals( + "Test", + searchRecords.find { record -> record.name == "class Test" }?.description ?: "" + ) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/SourceSetDependentHintTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/SourceSetDependentHintTest.kt new file mode 100644 index 00000000..e3c28984 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/SourceSetDependentHintTest.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.Platform +import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jetbrains.dokka.pages.TextStyle +import renderers.testPage +import testApi.testRunner.defaultSourceSet +import utils.Div +import utils.match +import java.io.File +import kotlin.test.Test + +class SourceSetDependentHintTest : HtmlRenderingOnlyTestBase() { + + private val pl1 = defaultSourceSet.copy( + "pl1", + defaultSourceSet.sourceSetID.copy(sourceSetName = "pl1"), + analysisPlatform = Platform.js, + sourceRoots = setOf(File("pl1")) + ) + private val pl2 = defaultSourceSet.copy( + "pl2", + defaultSourceSet.sourceSetID.copy(sourceSetName = "pl2"), + analysisPlatform = Platform.jvm, + sourceRoots = setOf(File("pl1")) + ) + private val pl3 = defaultSourceSet.copy( + "pl3", + defaultSourceSet.sourceSetID.copy(sourceSetName = "pl3"), + analysisPlatform = Platform.native, + sourceRoots = setOf(File("pl1")) + ) + + @Test + fun platformIndependentCase() { + val page = testPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2, pl3), styles = setOf(TextStyle.Block)) { + text("a") + text("b") + text("c") + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div("abc")))) + } + + @Test + fun completelyDivergentCase() { + val page = testPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2, pl3), styles = setOf(TextStyle.Block)) { + text("a", sourceSets = setOf(pl1)) + text("b", sourceSets = setOf(pl2)) + text("c", sourceSets = setOf(pl3)) + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div("a")), Div(Div("b")), Div(Div("c")))) + } + + @Test + fun overlappingCase() { + val page = testPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2), styles = setOf(TextStyle.Block)) { + text("a", sourceSets = setOf(pl1)) + text("b", sourceSets = setOf(pl1, pl2)) + text("c", sourceSets = setOf(pl2)) + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div("ab")), Div(Div("bc")))) + } + + @Test + fun caseThatCanBeSimplified() { + val page = testPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2), styles = setOf(TextStyle.Block)) { + text("a", sourceSets = setOf(pl1, pl2)) + text("b", sourceSets = setOf(pl1)) + text("b", sourceSets = setOf(pl2)) + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div("ab")))) + } + + @Test + fun caseWithGroupBreakingSimplification() { + val page = testPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2), styles = setOf(TextStyle.Block)) { + group(styles = setOf(TextStyle.Block)) { + text("a", sourceSets = setOf(pl1, pl2)) + text("b", sourceSets = setOf(pl1)) + } + text("b", sourceSets = setOf(pl2)) + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div(Div("ab"))), Div(Div(Div("a"), "b")))) + } + + @Test + fun caseWithGroupNotBreakingSimplification() { + val page = testPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2)) { + group { + text("a", sourceSets = setOf(pl1, pl2)) + text("b", sourceSets = setOf(pl1)) + } + text("b", sourceSets = setOf(pl2)) + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div("ab"))) + } + + @Test + fun partiallyUnifiedCase() { + val page = testPage { + sourceSetDependentHint(sourceSets = setOf(pl1, pl2, pl3), styles = setOf(TextStyle.Block)) { + text("a", sourceSets = setOf(pl1)) + text("a", sourceSets = setOf(pl2)) + text("b", sourceSets = setOf(pl3)) + } + } + + HtmlRenderer(context).render(page) + renderedContent.match(Div(Div(Div("a")), Div(Div("b")))) + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/SourceSetFilterTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/SourceSetFilterTest.kt new file mode 100644 index 00000000..b461bfcd --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/SourceSetFilterTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import signatures.renderedContent +import utils.TestOutputWriterPlugin +import kotlin.test.Test +import kotlin.test.assertEquals + +class SourceSetFilterTest : BaseAbstractTest() { + + @Test // see #3011 + fun `should separate multiple data-filterable attribute values with comma`() { + val configuration = dokkaConfiguration { + moduleName = "Dokka Module" + + sourceSets { + val common = sourceSet { + name = "common" + displayName = "common" + analysisPlatform = "common" + sourceRoots = listOf("src/commonMain/kotlin/testing/Test.kt") + } + sourceSet { + name = "jvm" + displayName = "jvm" + analysisPlatform = "jvm" + dependentSourceSets = setOf(common.value.sourceSetID) + sourceRoots = listOf("src/jvmMain/kotlin/testing/Test.kt") + } + } + } + + val source = """ + |/src/commonMain/kotlin/testing/Test.kt + |package testing + | + |expect open class Test + | + |/src/jvmMain/kotlin/testing/Test.kt + |package testing + | + |actual open class Test + """.trimIndent() + + val writerPlugin = TestOutputWriterPlugin() + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val packagePage = writerPlugin.writer.renderedContent("-dokka -module/testing/index.html") + + val testClassRow = packagePage + .select("div[data-togglable=TYPE]") + .select("div[class=table-row]") + .single() + + assertEquals("Dokka Module/common,Dokka Module/jvm", testClassRow.attr("data-filterable-current")) + assertEquals("Dokka Module/common,Dokka Module/jvm", testClassRow.attr("data-filterable-set")) + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/TabbedContentTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/TabbedContentTest.kt new file mode 100644 index 00000000..090127fd --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/TabbedContentTest.kt @@ -0,0 +1,188 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jsoup.nodes.Element +import signatures.renderedContent +import utils.TestOutputWriterPlugin +import kotlin.test.Test +import kotlin.test.assertEquals + +class TabbedContentTest : BaseAbstractTest() { + + private val configuration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + classpath = listOf(commonStdlibPath!!) + externalDocumentationLinks = listOf(stdlibExternalDocumentationLink) + } + } + } + + private fun Element.getTabbedRow(type: String) = select(".table-row[data-togglable=$type]") + private fun Element.getTabbedTable(type: String) = select("div[data-togglable=$type] .table") + private fun Element.getMainContentDataType() = selectFirst(".main-content")?.attr("data-page-type") + + @Test + fun `should have correct tabbed content type`() { + val source = """ + |/src/main/kotlin/test/Test.kt + |package example + | + |val p = 0 + |fun foo() = 0 + | + | class A(val d: Int = 0) { + | class Success(): Result() + | class Failed(): Result() + | + | fun fn() = 0 + | } + | + | fun A.fn() = 0 + | fun A.fn2() = 0 + | fun A.fn3() = 0 + | val A.p = 0 + | val A.p2 = 0 + """ + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val classContent = writerPlugin.writer.renderedContent("root/example/-a/index.html") + assertEquals(1, classContent.getTabbedTable("CONSTRUCTOR").size) + assertEquals(1, classContent.getTabbedTable("PROPERTY").size) + assertEquals(1, classContent.getTabbedTable("CONSTRUCTOR").size) + assertEquals(1, classContent.getTabbedTable("FUNCTION").size) + assertEquals(1, classContent.getTabbedTable("TYPE").size) + assertEquals(3, classContent.getTabbedRow("EXTENSION_FUNCTION").size) + assertEquals(2, classContent.getTabbedRow("EXTENSION_PROPERTY").size) + assertEquals("classlike", classContent.getMainContentDataType()) + + val packagePage = writerPlugin.writer.renderedContent("root/example/index.html") + assertEquals(1, packagePage.getTabbedTable("TYPE").size) + assertEquals(1, packagePage.getTabbedTable("PROPERTY").size) + assertEquals(1, packagePage.getTabbedTable("FUNCTION").size) + assertEquals(3, packagePage.getTabbedRow("EXTENSION_FUNCTION").size) + assertEquals(2, packagePage.getTabbedRow("EXTENSION_PROPERTY").size) + assertEquals("package", packagePage.getMainContentDataType()) + } + } + } + + @Test + fun `should not have Types-tab where there are not types`() { + val source = """ + |/src/main/kotlin/test/Test.kt + |package example + | + |val p = 0 + |fun foo() = 0 + | + |/src/main/kotlin/test/PackageTwo.kt + |package example2 + | + |class A + """ + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val packagePage = writerPlugin.writer.renderedContent("root/example/index.html") + assertEquals(0, packagePage.select("*[data-togglable=TYPE]").size) + assertEquals(1, packagePage.getTabbedTable("PROPERTY").size) + assertEquals(1, packagePage.getTabbedTable("FUNCTION").size) + + val packagePage2 = writerPlugin.writer.renderedContent("root/example2/index.html") + assertEquals(2, packagePage2.select("*[data-togglable=TYPE]").size) + assertEquals(0, packagePage2.getTabbedTable("PROPERTY").size) + assertEquals(0, packagePage2.getTabbedTable("FUNCTION").size) + } + } + } + + @Test + fun `should have correct order of members and extensions`() { + val source = """ + |/src/main/kotlin/test/Test.kt + |package example + | + |val p = 0 + |fun foo() = 0 + | + |class A(val d: Int = 0) { + | fun fn() = 0 + | fun a() = 0 + | fun g() = 0 + |} + | + | fun A.fn() = 0 + """ + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val classContent = writerPlugin.writer.renderedContent("root/example/-a/index.html") + val funTable = classContent.select("div[data-togglable=FUNCTION] .table") + val orders = + funTable.select(".table-row").map { it.attr("data-togglable") } + assertEquals(listOf("", "", "EXTENSION_FUNCTION", ""), orders) + val names = + funTable.select(".main-subrow .inline-flex a").map { it.text() } + assertEquals(listOf("a", "fn", "fn", "g"), names) + } + } + } + + @Test + fun `should have expected order of content types within a members tab`() { + val source = """ + |/src/main/kotlin/test/Result.kt + |package example + | + |class Result(val d: Int = 0) { + | class Success(): Result() + | + | val isFailed = false + | fun reset() = 0 + | fun String.extension() = 0 + |} + """ + val writerPlugin = TestOutputWriterPlugin() + + testInline( + source, + configuration, + pluginOverrides = listOf(writerPlugin) + ) { + renderingStage = { _, _ -> + val classContent = writerPlugin.writer.renderedContent("root/example/-result/index.html") + val tabSectionNames = classContent.select("div .tabs-section-body > div[data-togglable]") + .map { it.attr("data-togglable") } + + val expectedOrder = listOf("CONSTRUCTOR", "TYPE", "PROPERTY", "FUNCTION") + + assertEquals(expectedOrder.size, tabSectionNames.size) + expectedOrder.forEachIndexed { index, element -> + assertEquals(element, tabSectionNames[index]) + } + } + } + } +} diff --git a/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/TextStylesTest.kt b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/TextStylesTest.kt new file mode 100644 index 00000000..0ca4e245 --- /dev/null +++ b/dokka-subprojects/plugin-base/src/test/kotlin/renderers/html/TextStylesTest.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package renderers.html + +import org.jetbrains.dokka.base.renderers.html.HtmlRenderer +import org.jetbrains.dokka.pages.TextStyle +import org.jetbrains.dokka.pages.TokenStyle +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import renderers.testPage +import utils.* +import kotlin.test.Test +import kotlin.test.assertEquals + +class TextStylesTest : HtmlRenderingOnlyTestBase() { + @Test + fun `should include bold`(){ + val page = testPage { + text("bold text", styles = setOf(TextStyle.Bold)) + } + HtmlRenderer(context).render(page) + renderedContent.match(B("bold text")) + } + + @Test + fun `should include italics`(){ + val page = testPage { + text("italics text", styles = setOf(TextStyle.Italic)) + } + HtmlRenderer(context).render(page) + renderedContent.match(I("italics text")) + } + + @Test + fun `should include strikethrought`(){ + val page = testPage { + text("strike text", styles = setOf(TextStyle.Strikethrough)) + } + HtmlRenderer(context).render(page) + renderedContent.match(STRIKE("strike text")) + } + + @Test + fun `should include token styles`(){ + val page = testPage { + text("keyword", styles = setOf(TokenStyle.Keyword)) + } + HtmlRenderer(context).render(page) + renderedContent.match(Span("keyword")) + val lastChild = renderedContent.children().last() ?: throw IllegalStateException("No element found") + assertEquals(lastChild.attr("class"), "token keyword") + } + + @Test + fun `should include multiple styles at one`(){ + val page = testPage { + text( + "styled text", + styles = setOf( + TextStyle.Strikethrough, + TextStyle.Bold, + TextStyle.Indented, + TextStyle.UnderCoverText, + TextStyle.BreakableAfter + ) + ) + } + HtmlRenderer(context).render(page) + renderedContent.match(STRIKE(B("styled text"))) + //Our dsl swallows nbsp so i manually check for it + files.contents.getValue("test-page.html").contains(" <strike><b>styled text</b></strike>") + } + + @Test + fun `should include blockquote`() { + val page = testPage { + group(styles = setOf(TextStyle.Quotation)) { + text("blockquote text") + } + } + HtmlRenderer(context).render(page) + renderedContent.match(BlockQuote("blockquote text")) + } + + @Test + fun `should include var`() { + val page = testPage { + group(styles = setOf(TextStyle.Var)) { + text("variable") + } + } + HtmlRenderer(context).render(page) + println(renderedContent) + renderedContent.match(Var("variable")) + } + + @Test + fun `should include underlined text`() { + val page = testPage { + group(styles = setOf(TextStyle.Underlined)) { + text("underlined text") + } + } + HtmlRenderer(context).render(page) + println(renderedContent) + renderedContent.match(U("underlined text")) + } + + override val renderedContent: Element + get() = files.contents.getValue("test-page.html").let { Jsoup.parse(it) }.select("#content").single() +} |