diff --git a/plugins/base/api/base.api b/plugins/base/api/base.api index 083c9973cc..bf5dcff727 100644 --- a/plugins/base/api/base.api +++ b/plugins/base/api/base.api @@ -400,22 +400,44 @@ public abstract class org/jetbrains/dokka/base/renderers/html/NavigationDataProv } public final class org/jetbrains/dokka/base/renderers/html/NavigationNode : org/jetbrains/dokka/model/WithChildren { - public fun (Ljava/lang/String;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Ljava/util/List;)V + public fun (Ljava/lang/String;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;Ljava/util/List;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Lorg/jetbrains/dokka/links/DRI; public final fun component3 ()Ljava/util/Set; - public final fun component4 ()Ljava/util/List; - public final fun copy (Ljava/lang/String;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Ljava/util/List;)Lorg/jetbrains/dokka/base/renderers/html/NavigationNode; - public static synthetic fun copy$default (Lorg/jetbrains/dokka/base/renderers/html/NavigationNode;Ljava/lang/String;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/dokka/base/renderers/html/NavigationNode; + public final fun component4 ()Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/String;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;Ljava/util/List;)Lorg/jetbrains/dokka/base/renderers/html/NavigationNode; + public static synthetic fun copy$default (Lorg/jetbrains/dokka/base/renderers/html/NavigationNode;Ljava/lang/String;Lorg/jetbrains/dokka/links/DRI;Ljava/util/Set;Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon;Ljava/util/List;ILjava/lang/Object;)Lorg/jetbrains/dokka/base/renderers/html/NavigationNode; public fun equals (Ljava/lang/Object;)Z public fun getChildren ()Ljava/util/List; public final fun getDri ()Lorg/jetbrains/dokka/links/DRI; + public final fun getIcon ()Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; public final fun getName ()Ljava/lang/String; public final fun getSourceSets ()Ljava/util/Set; public fun hashCode ()I public fun toString ()Ljava/lang/String; } +public final class org/jetbrains/dokka/base/renderers/html/NavigationNodeIcon : java/lang/Enum { + public static final field ABSTRACT_CLASS Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field ABSTRACT_CLASS_KT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field ANNOTATION_CLASS Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field ANNOTATION_CLASS_KT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field CLASS Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field CLASS_KT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field ENUM_CLASS Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field ENUM_CLASS_KT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field EXCEPTION Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field FUNCTION Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field INTERFACE Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field INTERFACE_KT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field OBJECT Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field VAL Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static final field VAR Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; + public static fun values ()[Lorg/jetbrains/dokka/base/renderers/html/NavigationNodeIcon; +} + public final class org/jetbrains/dokka/base/renderers/html/NavigationPage : org/jetbrains/dokka/pages/RendererSpecificPage { public fun (Lorg/jetbrains/dokka/base/renderers/html/NavigationNode;Ljava/lang/String;Lorg/jetbrains/dokka/plugability/DokkaContext;)V public fun getChildren ()Ljava/util/List; diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt new file mode 100644 index 0000000000..647ba6874d --- /dev/null +++ b/plugins/base/src/main/kotlin/renderers/html/NavigationDataProvider.kt @@ -0,0 +1,90 @@ +package org.jetbrains.dokka.base.renderers.html + +import org.jetbrains.dokka.base.renderers.sourceSets +import org.jetbrains.dokka.base.transformers.documentables.isException +import org.jetbrains.dokka.base.translators.documentables.DocumentableLanguage +import org.jetbrains.dokka.base.translators.documentables.documentableLanguage +import org.jetbrains.dokka.model.* +import org.jetbrains.dokka.pages.* + +abstract class NavigationDataProvider { + open fun navigableChildren(input: RootPageNode): NavigationNode = input.withDescendants() + .first { it is ModulePage || it is MultimoduleRootPage }.let { visit(it as ContentPage) } + + open fun visit(page: ContentPage): NavigationNode = + NavigationNode( + name = page.displayableName(), + dri = page.dri.first(), + sourceSets = page.sourceSets(), + icon = chooseNavigationIcon(page), + children = page.navigableChildren() + ) + + /** + * Parenthesis is applied in 1 case: + * - page only contains functions (therefore documentable from this page is [DFunction]) + */ + private fun ContentPage.displayableName(): String = + if (this is WithDocumentables && documentables.all { it is DFunction }) { + "$name()" + } else { + name + } + + private fun chooseNavigationIcon(contentPage: ContentPage): NavigationNodeIcon? { + return if (contentPage is WithDocumentables) { + val documentable = contentPage.documentables.firstOrNull() + val isJava = documentable?.hasAnyJavaSources() ?: false + + when (documentable) { + is DClass -> when { + documentable.isException -> NavigationNodeIcon.EXCEPTION + documentable.isAbstract() -> { + if (isJava) NavigationNodeIcon.ABSTRACT_CLASS else NavigationNodeIcon.ABSTRACT_CLASS_KT + } + else -> if (isJava) NavigationNodeIcon.CLASS else NavigationNodeIcon.CLASS_KT + } + is DFunction -> NavigationNodeIcon.FUNCTION + is DProperty -> { + val isVar = documentable.extra[IsVar] != null + if (isVar) NavigationNodeIcon.VAR else NavigationNodeIcon.VAL + } + is DInterface -> if (isJava) NavigationNodeIcon.INTERFACE else NavigationNodeIcon.INTERFACE_KT + is DEnum, + is DEnumEntry -> if (isJava) NavigationNodeIcon.ENUM_CLASS else NavigationNodeIcon.ENUM_CLASS_KT + is DAnnotation -> { + if (isJava) NavigationNodeIcon.ANNOTATION_CLASS else NavigationNodeIcon.ANNOTATION_CLASS_KT + } + is DObject -> NavigationNodeIcon.OBJECT + else -> null + } + } else { + null + } + } + + private fun Documentable.hasAnyJavaSources(): Boolean { + val withSources = this as? WithSources ?: return false + return this.sourceSets.any { withSources.documentableLanguage(it) == DocumentableLanguage.JAVA } + } + + private fun DClass.isAbstract(): Boolean { + return modifier.values.all { it is KotlinModifier.Abstract || it is JavaModifier.Abstract } + } + + private fun ContentPage.navigableChildren(): List { + return if (this !is ClasslikePageNode) { + children + .filterIsInstance() + .map { visit(it) } + .sortedBy { it.name.toLowerCase() } + } else if (documentables.any { it is DEnum }) { + // no sorting for enum entries, should be the same as in source code + children + .filter { child -> child is WithDocumentables && child.documentables.any { it is DEnumEntry } } + .map { visit(it as ContentPage) } + } else { + emptyList() + } + } +} diff --git a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt index e0b20f01a7..e51836997a 100644 --- a/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt +++ b/plugins/base/src/main/kotlin/renderers/html/NavigationPage.kt @@ -7,13 +7,15 @@ import org.jetbrains.dokka.base.templating.AddToNavigationCommand import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.model.DisplaySourceSet import org.jetbrains.dokka.model.WithChildren -import org.jetbrains.dokka.pages.PageNode -import org.jetbrains.dokka.pages.RendererSpecificPage -import org.jetbrains.dokka.pages.RenderingStrategy +import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.plugability.DokkaContext -class NavigationPage(val root: NavigationNode, val moduleName: String, val context: DokkaContext) : - RendererSpecificPage { +class NavigationPage( + val root: NavigationNode, + val moduleName: String, + val context: DokkaContext +) : RendererSpecificPage { + override val name = "navigation" override val children = emptyList() @@ -46,22 +48,69 @@ class NavigationPage(val root: NavigationNode, val moduleName: String, val conte span("navButtonContent") } } - buildLink(node.dri, node.sourceSets.toList()) { buildBreakableText(node.name) } + buildLink(node.dri, node.sourceSets.toList()) { + // special condition for Enums as it has children enum entries in navigation + val withIcon = node.icon != null && (node.children.isEmpty() || node.isEnum()) + if (withIcon) { + // in case link text is so long that it needs to have word breaks, + // and it stretches to two or more lines, make sure the icon + // is always on the left in the grid and is not wrapped with text + span("nav-link-grid") { + span("nav-link-child ${node.icon?.style()}") + span("nav-link-child") { + buildBreakableText(node.name) + } + } + } else { + buildBreakableText(node.name) + } + } } node.children.withIndex().forEach { (n, p) -> visit(p, "$navId-$n", renderer) } } } + + private fun NavigationNode.isEnum(): Boolean { + return icon == NavigationNodeIcon.ENUM_CLASS || icon == NavigationNodeIcon.ENUM_CLASS_KT + } } data class NavigationNode( val name: String, val dri: DRI, val sourceSets: Set, + val icon: NavigationNodeIcon?, override val children: List ) : WithChildren +/** + * [CLASS] represents a neutral (a.k.a Java-style) icon, + * whereas [CLASS_KT] should be Kotlin-styled + */ +enum class NavigationNodeIcon( + private val cssClass: String +) { + CLASS("class"), + CLASS_KT("class-kt"), + ABSTRACT_CLASS("abstract-class"), + ABSTRACT_CLASS_KT("abstract-class-kt"), + ENUM_CLASS("enum-class"), + ENUM_CLASS_KT("enum-class-kt"), + ANNOTATION_CLASS("annotation-class"), + ANNOTATION_CLASS_KT("annotation-class-kt"), + INTERFACE("interface"), + INTERFACE_KT("interface-kt"), + FUNCTION("function"), + EXCEPTION("exception-class"), + OBJECT("object"), + VAL("val"), + VAR("var"); + + internal fun style(): String = "nav-icon $cssClass" +} + fun NavigationPage.transform(block: (NavigationNode) -> NavigationNode) = NavigationPage(root.transform(block), moduleName, context) fun NavigationNode.transform(block: (NavigationNode) -> NavigationNode) = - run(block).let { NavigationNode(it.name, it.dri, it.sourceSets, it.children.map(block)) } + run(block).let { NavigationNode(it.name, it.dri, it.sourceSets, it.icon, it.children.map(block)) } diff --git a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt index 4527baa79e..2de6f2b7ed 100644 --- a/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt +++ b/plugins/base/src/main/kotlin/renderers/html/htmlPreprocessors.kt @@ -2,60 +2,16 @@ package org.jetbrains.dokka.base.renderers.html import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.DokkaBaseConfiguration -import org.jetbrains.dokka.base.renderers.sourceSets import org.jetbrains.dokka.base.templating.AddToSourcesetDependencies import org.jetbrains.dokka.base.templating.toJsonString -import org.jetbrains.dokka.model.DEnum -import org.jetbrains.dokka.model.DEnumEntry -import org.jetbrains.dokka.model.DFunction -import org.jetbrains.dokka.model.withDescendants -import org.jetbrains.dokka.pages.* +import org.jetbrains.dokka.pages.RendererSpecificResourcePage +import org.jetbrains.dokka.pages.RenderingStrategy +import org.jetbrains.dokka.pages.RootPageNode import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.configuration import org.jetbrains.dokka.transformers.pages.PageTransformer -abstract class NavigationDataProvider { - open fun navigableChildren(input: RootPageNode): NavigationNode = input.withDescendants() - .first { it is ModulePage || it is MultimoduleRootPage }.let { visit(it as ContentPage) } - - open fun visit(page: ContentPage): NavigationNode = - NavigationNode( - name = page.displayableName, - dri = page.dri.first(), - sourceSets = page.sourceSets(), - children = page.navigableChildren() - ) - - private fun ContentPage.navigableChildren(): List { - return if (this !is ClasslikePageNode) { - children - .filterIsInstance() - .map { visit(it) } - .sortedBy { it.name.toLowerCase() } - } else if (documentables.any { it is DEnum }) { - // no sorting for enum entries, should be the same as in source code - children - .filter { child -> child is WithDocumentables && child.documentables.any { it is DEnumEntry } } - .map { visit(it as ContentPage) } - } else { - emptyList() - } - } - - /** - * Parenthesis is applied in 1 case: - * - page only contains functions (therefore documentable from this page is [DFunction]) - */ - private val ContentPage.displayableName: String - get() = if (this is WithDocumentables && documentables.all { it is DFunction }) { - "$name()" - } else { - name - } -} - open class NavigationPageInstaller(val context: DokkaContext) : NavigationDataProvider(), PageTransformer { - override fun invoke(input: RootPageNode): RootPageNode = input.modified( children = input.children + NavigationPage( @@ -138,6 +94,23 @@ object AssetsInstaller : PageTransformer { "images/copy-icon.svg", "images/copy-successful-icon.svg", "images/theme-toggle.svg", + + // navigation icons + "images/nav-icons/abstract-class.svg", + "images/nav-icons/abstract-class-kotlin.svg", + "images/nav-icons/annotation.svg", + "images/nav-icons/annotation-kotlin.svg", + "images/nav-icons/class.svg", + "images/nav-icons/class-kotlin.svg", + "images/nav-icons/enum.svg", + "images/nav-icons/enum-kotlin.svg", + "images/nav-icons/exception-class.svg", + "images/nav-icons/field-value.svg", + "images/nav-icons/field-variable.svg", + "images/nav-icons/function.svg", + "images/nav-icons/interface.svg", + "images/nav-icons/interface-kotlin.svg", + "images/nav-icons/object.svg", ) override fun invoke(input: RootPageNode) = input.modified( diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/abstract-class-kotlin.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/abstract-class-kotlin.svg new file mode 100644 index 0000000000..a2069b8fb7 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/abstract-class-kotlin.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/abstract-class.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/abstract-class.svg new file mode 100644 index 0000000000..601820302f --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/abstract-class.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/annotation-kotlin.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/annotation-kotlin.svg new file mode 100644 index 0000000000..932f1d3de4 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/annotation-kotlin.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/annotation.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/annotation.svg new file mode 100644 index 0000000000..b80c54b4b0 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/annotation.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/class-kotlin.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/class-kotlin.svg new file mode 100644 index 0000000000..46a21f65a0 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/class-kotlin.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/class.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/class.svg new file mode 100644 index 0000000000..3f1ad167e7 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/class.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/enum-kotlin.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/enum-kotlin.svg new file mode 100644 index 0000000000..4a85459683 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/enum-kotlin.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/enum.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/enum.svg new file mode 100644 index 0000000000..fa7f24766d --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/enum.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/exception-class.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/exception-class.svg new file mode 100644 index 0000000000..c0b2bdeba7 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/exception-class.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/field-value.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/field-value.svg new file mode 100644 index 0000000000..20449c9475 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/field-value.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/field-variable.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/field-variable.svg new file mode 100644 index 0000000000..3b0745007f --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/field-variable.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/function.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/function.svg new file mode 100644 index 0000000000..f0da64a0b7 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/function.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/interface-kotlin.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/interface-kotlin.svg new file mode 100644 index 0000000000..bf07a1488e --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/interface-kotlin.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/interface.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/interface.svg new file mode 100644 index 0000000000..32063ba263 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/interface.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/images/nav-icons/object.svg b/plugins/base/src/main/resources/dokka/images/nav-icons/object.svg new file mode 100644 index 0000000000..9f427de413 --- /dev/null +++ b/plugins/base/src/main/resources/dokka/images/nav-icons/object.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/plugins/base/src/main/resources/dokka/scripts/navigation-loader.js b/plugins/base/src/main/resources/dokka/scripts/navigation-loader.js index 9c824b91c3..92464911c6 100644 --- a/plugins/base/src/main/resources/dokka/scripts/navigation-loader.js +++ b/plugins/base/src/main/resources/dokka/scripts/navigation-loader.js @@ -59,7 +59,10 @@ scrollNavigationToSelectedElement = () => { return } - let isPackageElement = selectedElement.children.length > 1 + let hasIcon = selectedElement.querySelectorAll(":scope > div.overview span.nav-icon").length > 0 + + // for instance enums also have children and are expandable, but are not package/module elements + let isPackageElement = selectedElement.children.length > 1 && !hasIcon if (isPackageElement) { // if package is selected or linked, it makes sense to align it to top // so that you can see all the members it contains diff --git a/plugins/base/src/main/resources/dokka/styles/style.css b/plugins/base/src/main/resources/dokka/styles/style.css index e32ec063af..6a9d2a87c6 100644 --- a/plugins/base/src/main/resources/dokka/styles/style.css +++ b/plugins/base/src/main/resources/dokka/styles/style.css @@ -271,13 +271,13 @@ p.paragraph:first-child, #main { width: 100%; - max-width: calc(100% - 280px); + max-width: calc(100% - 300px); display: flex; flex-direction: column; } #leftColumn { - width: 280px; + width: 300px; border-right: 1px solid var(--border-color); display: flex; flex-direction: column; @@ -448,7 +448,7 @@ code.paragraph { box-sizing: border-box; content: ''; top: 0; - width: 280px; + width: 300px; right: 0; bottom: 0; position: absolute; @@ -502,6 +502,78 @@ code.paragraph { display: none; } +.overview .nav-link-grid { + display: grid; + grid-template-columns: 16px auto; /* first is the icon, then name */ + grid-gap: 6px; + align-items: center; +} + +.nav-icon { + width: 16px; + height: 16px; +} + +.nav-icon.class::before { + content: url("../images/nav-icons/class.svg"); +} + +.nav-icon.class-kt::before { + content: url("../images/nav-icons/class-kotlin.svg"); +} + +.nav-icon.function::before { + content: url("../images/nav-icons/function.svg"); +} + +.nav-icon.enum-class::before { + content: url("../images/nav-icons/enum.svg"); +} + +.nav-icon.enum-class-kt::before { + content: url("../images/nav-icons/enum-kotlin.svg"); +} + +.nav-icon.annotation-class::before { + content: url("../images/nav-icons/annotation.svg"); +} + +.nav-icon.annotation-class-kt::before { + content: url("../images/nav-icons/annotation-kotlin.svg"); +} + +.nav-icon.abstract-class::before { + content: url("../images/nav-icons/abstract-class.svg"); +} + +.nav-icon.abstract-class-kt::before { + content: url("../images/nav-icons/abstract-class-kotlin.svg"); +} + +.nav-icon.exception-class::before { + content: url("../images/nav-icons/exception-class.svg"); +} + +.nav-icon.interface::before { + content: url("../images/nav-icons/interface.svg"); +} + +.nav-icon.interface-kt::before { + content: url("../images/nav-icons/interface-kotlin.svg"); +} + +.nav-icon.object::before { + content: url("../images/nav-icons/object.svg"); +} + +.nav-icon.val::before { + content: url("../images/nav-icons/field-value.svg"); +} + +.nav-icon.var::before { + content: url("../images/nav-icons/field-variable.svg"); +} + .filtered > a, .filtered > .navButton { display: none; } @@ -1118,7 +1190,7 @@ div.runnablesample { #leftColumn { position: fixed; - margin-left: -280px; + margin-left: -300px; transition: margin .2s ease-out; z-index: 4; background: white; @@ -1147,7 +1219,7 @@ div.runnablesample { } #leftColumn.open ~ #main #leftToggler { - margin-left: 280px; + margin-left: 300px; } .icon-toggler::before { diff --git a/plugins/base/src/test/kotlin/enums/JavaEnumTest.kt b/plugins/base/src/test/kotlin/enums/JavaEnumTest.kt deleted file mode 100644 index 369fbe7949..0000000000 --- a/plugins/base/src/test/kotlin/enums/JavaEnumTest.kt +++ /dev/null @@ -1,66 +0,0 @@ -package enums - -import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest -import org.jetbrains.dokka.model.DEnum -import org.jetbrains.dokka.model.ObviousMember -import org.junit.jupiter.api.Test -import utils.TestOutputWriterPlugin -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -class JavaEnumTest : BaseAbstractTest() { - - private val configuration = dokkaConfiguration { - sourceSets { - sourceSet { - sourceRoots = listOf("src/") - } - } - } - - @Test - fun `should mark synthetic functions generated for Kotlin as obvious`() { - val writerPlugin = TestOutputWriterPlugin() - testInline( - """ - |/src/main/java/basic/JavaEnum.java - |package testpackage - | - |public enum JavaEnum { - | ONE, TWO - |} - """.trimMargin(), - configuration, - pluginOverrides = listOf(writerPlugin) - ) { - documentablesCreationStage = { modules -> - val pckg = modules.flatMap { it.packages }.single { it.packageName == "testpackage" } - val enum = pckg.children.single { it is DEnum } as DEnum - - // there's two with the same name, one inherited from - // java.lang.Enum and one is synthetic for Kotlin interop - enum.functions.filter { it.name == "valueOf" }.let { valueOfMethods -> - assertEquals(2, valueOfMethods.size) - - val valueOfFromKotlin = valueOfMethods[0] - assertEquals( - "testpackage/JavaEnum/valueOf/#java.lang.String/PointingToDeclaration/", - valueOfFromKotlin.dri.toString() - ) - assertNotNull(valueOfFromKotlin.extra[ObviousMember]) - - val valueOfFromJava = valueOfMethods[1] - assertEquals( - "java.lang/Enum/valueOf/#java.lang.Class#java.lang.String/PointingToDeclaration/", - valueOfFromJava.dri.toString() - ) - assertNotNull(valueOfFromJava.extra[ObviousMember]) - } - - val valuesMethod = enum.functions.single { it.name == "values" } - assertEquals("testpackage/JavaEnum/values/#/PointingToDeclaration/", valuesMethod.dri.toString()) - assertNotNull(valuesMethod.extra[ObviousMember]) - } - } - } -} diff --git a/plugins/base/src/test/kotlin/enums/JavaEnumsTest.kt b/plugins/base/src/test/kotlin/enums/JavaEnumsTest.kt index 6a59a57eaf..939163ca01 100644 --- a/plugins/base/src/test/kotlin/enums/JavaEnumsTest.kt +++ b/plugins/base/src/test/kotlin/enums/JavaEnumsTest.kt @@ -2,14 +2,25 @@ package enums import org.jetbrains.dokka.SourceLinkDefinitionImpl import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jetbrains.dokka.model.DEnum +import org.jetbrains.dokka.model.ObviousMember import org.junit.jupiter.api.Test import signatures.renderedContent import utils.TestOutputWriterPlugin import java.net.URL import kotlin.test.assertEquals +import kotlin.test.assertNotNull class JavaEnumsTest : BaseAbstractTest() { + private val basicConfiguration = dokkaConfiguration { + sourceSets { + sourceSet { + sourceRoots = listOf("src/") + } + } + } + // Shouldn't try to give source links to synthetic methods (values, valueOf) if any are present // https://github.com/Kotlin/dokka/issues/2544 @Test @@ -57,4 +68,50 @@ class JavaEnumsTest : BaseAbstractTest() { } } } + + @Test + fun `should mark synthetic functions generated for Kotlin as obvious`() { + val writerPlugin = TestOutputWriterPlugin() + testInline( + """ + |/src/main/java/basic/JavaEnum.java + |package testpackage + | + |public enum JavaEnum { + | ONE, TWO + |} + """.trimMargin(), + basicConfiguration, + pluginOverrides = listOf(writerPlugin) + ) { + documentablesCreationStage = { modules -> + val pckg = modules.flatMap { it.packages }.single { it.packageName == "testpackage" } + val enum = pckg.children.single { it is DEnum } as DEnum + + // there's two with the same name, one inherited from + // java.lang.Enum and one is synthetic for Kotlin interop + enum.functions.filter { it.name == "valueOf" }.let { valueOfMethods -> + assertEquals(2, valueOfMethods.size) + + val valueOfFromKotlin = valueOfMethods[0] + assertEquals( + "testpackage/JavaEnum/valueOf/#java.lang.String/PointingToDeclaration/", + valueOfFromKotlin.dri.toString() + ) + assertNotNull(valueOfFromKotlin.extra[ObviousMember]) + + val valueOfFromJava = valueOfMethods[1] + assertEquals( + "java.lang/Enum/valueOf/#java.lang.Class#java.lang.String/PointingToDeclaration/", + valueOfFromJava.dri.toString() + ) + assertNotNull(valueOfFromJava.extra[ObviousMember]) + } + + val valuesMethod = enum.functions.single { it.name == "values" } + assertEquals("testpackage/JavaEnum/values/#/PointingToDeclaration/", valuesMethod.dri.toString()) + assertNotNull(valuesMethod.extra[ObviousMember]) + } + } + } } diff --git a/plugins/base/src/test/kotlin/enums/KotlinEnumTest.kt b/plugins/base/src/test/kotlin/enums/KotlinEnumTest.kt deleted file mode 100644 index 8343086904..0000000000 --- a/plugins/base/src/test/kotlin/enums/KotlinEnumTest.kt +++ /dev/null @@ -1,408 +0,0 @@ -package enums - -import matchers.content.* -import org.jetbrains.dokka.pages.* -import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest -import org.jetbrains.dokka.model.* -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance -import org.jsoup.Jsoup -import org.jsoup.nodes.Element -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test -import signatures.renderedContent -import utils.TestOutputWriter -import utils.TestOutputWriterPlugin - -class KotlinEnumTest : 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 - 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.table[data-togglable=Entries]") - .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 `should contain synthetic values and valueOf functions`() { - val configuration = dokkaConfiguration { - sourceSets { - sourceSet { - sourceRoots = listOf("src/") - } - } - } - - testInline( - """ - |/src/main/kotlin/basic/Test.kt - |package testpackage - | - |enum class TestEnum { - | E1, - | E2; - |} - """.trimMargin(), - configuration - ) { - // stage is important because they will get filtered out later on - documentablesCreationStage = { modules -> - val pckg = modules.flatMap { it.packages }.single { it.packageName == "testpackage" } - val enum = pckg.children.single { it is DEnum } as DEnum - - val valueOf = enum.functions.single { it.name == "valueOf" } - assertEquals("testpackage/TestEnum/valueOf/#kotlin.String/PointingToDeclaration/", valueOf.dri.toString()) - assertNotNull(valueOf.extra[ObviousMember]) - - val values = enum.functions.single { it.name == "values" } - assertEquals("testpackage/TestEnum/values/#/PointingToDeclaration/", values.dri.toString()) - assertNotNull(values.extra[ObviousMember]) - } - } - } - - @Test - fun enumWithMethods() { - 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 - ) { - 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 - 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.firstIsInstance() - 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" - } - } - } - } - } - } - } - } -} diff --git a/plugins/base/src/test/kotlin/renderers/html/NavigationIconTest.kt b/plugins/base/src/test/kotlin/renderers/html/NavigationIconTest.kt new file mode 100644 index 0000000000..f2c1fca84c --- /dev/null +++ b/plugins/base/src/test/kotlin/renderers/html/NavigationIconTest.kt @@ -0,0 +1,286 @@ +package renderers.html + +import org.jetbrains.dokka.base.testApi.testRunner.BaseAbstractTest +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import org.jsoup.select.Elements +import org.junit.jupiter.api.Test +import utils.TestOutputWriter +import utils.TestOutputWriterPlugin +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(15, 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]) + } + } + } + + @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 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) + } + } + } + + private fun TestOutputWriter.navigationHtml(): Element = contents.getValue("navigation.html").let { Jsoup.parse(it) } + + private fun Elements.selectNavigationGrid(): Element { + return this.select("div.overview").select("span.nav-link-grid").single() + } +}