diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/StructurizrUtilities.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/StructurizrUtilities.kt index cef8d7dd..3138a6b5 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/StructurizrUtilities.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/StructurizrUtilities.kt @@ -25,11 +25,10 @@ val SoftwareSystem.includedProperties val Container.hasComponents get() = this.components.isNotEmpty() -fun SoftwareSystem.firstContainerName(generatorContext: GeneratorContext) = containers - .firstOrNull { container -> +fun SoftwareSystem.firstContainer(generatorContext: GeneratorContext) = containers + .sortedBy { it.name }.firstOrNull { container -> generatorContext.workspace.hasComponentDiagrams(container) or generatorContext.workspace.hasImageViews(container.id) } - ?.name?.normalize() fun SoftwareSystem.hasDecisions() = documentation.decisions.isNotEmpty() @@ -39,6 +38,11 @@ fun SoftwareSystem.hasDocumentationSections() = documentation.sections.size >= 2 fun SoftwareSystem.hasContainerDocumentationSections() = containers.any { it.hasSections() } +fun Container.firstComponent(generatorContext: GeneratorContext) = components + .sortedBy { it.name }.firstOrNull { + component -> generatorContext.workspace.hasImageViews(component.id) } + + fun Container.hasDecisions() = documentation.decisions.isNotEmpty() fun Container.hasSections() = documentation.sections.isNotEmpty() diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/PlantUmlExporter.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/PlantUmlExporter.kt index 520b4f4c..50456ed1 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/PlantUmlExporter.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/PlantUmlExporter.kt @@ -109,7 +109,7 @@ private class WriterWithElementLinks( val path = when (element) { is SoftwareSystem -> "/${element.name?.normalize()}/${page?.let { page } ?: "container"}/".asUrlToDirectory(url) is Container -> "/${element.parent?.name?.normalize()}/${page?.let { page } ?: "component"}/${element.name?.normalize()}".asUrlToDirectory(url) - is Component -> "/${element.parent?.parent?.name?.normalize()}/${page?.let { page } ?: "code"}/".asUrlToDirectory(url) + is Component -> "/${element.parent?.parent?.name?.normalize()}/${page?.let { page } ?: "code"}/${element.container?.name?.normalize()}/${element.name?.normalize()}".asUrlToDirectory(url) else -> throw IllegalStateException("Not supported element") } return "$TEMP_URI$path" diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/SiteGenerator.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/SiteGenerator.kt index 8c8fa88e..e46869de 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/SiteGenerator.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/SiteGenerator.kt @@ -4,6 +4,8 @@ import com.structurizr.Workspace import com.structurizr.util.WorkspaceUtils import kotlinx.html.* import kotlinx.html.stream.appendHTML +import nl.avisi.structurizr.site.generatr.hasComponentDiagrams +import nl.avisi.structurizr.site.generatr.hasImageViews import nl.avisi.structurizr.site.generatr.includedSoftwareSystems import nl.avisi.structurizr.site.generatr.site.model.* import nl.avisi.structurizr.site.generatr.site.views.* @@ -144,7 +146,6 @@ private fun generateHtmlFiles(context: GeneratorContext, branchDir: File) { add { writeHtmlFile(branchDir, SoftwareSystemHomePageViewModel(context, it)) } add { writeHtmlFile(branchDir, SoftwareSystemContextPageViewModel(context, it)) } add { writeHtmlFile(branchDir, SoftwareSystemContainerPageViewModel(context, it)) } - add { writeHtmlFile(branchDir, SoftwareSystemCodePageViewModel(context, it)) } add { writeHtmlFile(branchDir, SoftwareSystemDynamicPageViewModel(context, it)) } add { writeHtmlFile(branchDir, SoftwareSystemDeploymentPageViewModel(context, it)) } add { writeHtmlFile(branchDir, SoftwareSystemDependenciesPageViewModel(context, it)) } @@ -180,6 +181,18 @@ private fun generateHtmlFiles(context: GeneratorContext, branchDir: File) { .forEach { container -> add { writeHtmlFile(branchDir, SoftwareSystemContainerComponentsPageViewModel(context, container)) } } + it.containers + .filter { container -> + ( context.workspace.hasComponentDiagrams(container) or + context.workspace.hasImageViews(container.id)) } + .forEach { container -> + container.components.filter { component -> + context.workspace.hasImageViews(component.id) } + .forEach { component -> + add { writeHtmlFile(branchDir, SoftwareSystemContainerComponentCodePageViewModel(context, container, component)) } + } + } + it.documentation.sections.filter { section -> section.order != 1 }.forEach { section -> add { writeHtmlFile(branchDir, SoftwareSystemSectionPageViewModel(context, it, section)) } } @@ -205,7 +218,7 @@ private fun writeHtmlFile(exportDir: File, viewModel: PageViewModel) { is SoftwareSystemContainerSectionPageViewModel -> softwareSystemContainerSectionPage(viewModel) is SoftwareSystemContainerSectionsPageViewModel -> softwareSystemContainerSectionsPage(viewModel) is SoftwareSystemContainerComponentsPageViewModel -> softwareSystemContainerComponentsPage(viewModel) - is SoftwareSystemCodePageViewModel -> softwareSystemCodePage(viewModel) + is SoftwareSystemContainerComponentCodePageViewModel -> softwareSystemContainerComponentCodePage(viewModel) is SoftwareSystemDynamicPageViewModel -> softwareSystemDynamicPage(viewModel) is SoftwareSystemDeploymentPageViewModel -> softwareSystemDeploymentPage(viewModel) is SoftwareSystemDependenciesPageViewModel -> softwareSystemDependenciesPage(viewModel) diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ComponentTabViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ComponentTabViewModel.kt new file mode 100644 index 00000000..ce041ff9 --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ComponentTabViewModel.kt @@ -0,0 +1,5 @@ +package nl.avisi.structurizr.site.generatr.site.model + +data class ComponentTabViewModel(val pageViewModel: PageViewModel, val title: String, val url: String) { + val link = LinkViewModel(pageViewModel, title, url) +} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ComponentsTabViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ComponentsTabViewModel.kt new file mode 100644 index 00000000..ad38de2e --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ComponentsTabViewModel.kt @@ -0,0 +1,24 @@ +package nl.avisi.structurizr.site.generatr.site.model + +import com.structurizr.model.Container +import nl.avisi.structurizr.site.generatr.hasImageViews +import nl.avisi.structurizr.site.generatr.site.GeneratorContext + +fun SoftwareSystemPageViewModel.createComponentsTabViewModel( + generatorContext: GeneratorContext, + container: Container +) = buildList { + container + .components + .sortedBy { it.name } + .filter { component -> + generatorContext.workspace.hasImageViews(component.id) } + .map { + ComponentTabViewModel( + this@createComponentsTabViewModel, + it.name, + SoftwareSystemContainerComponentCodePageViewModel.url(it.container, it) + ) + } + .forEach { add(it) } +} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainerTabViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainerTabViewModel.kt index 02d75c04..83f4f0ec 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainerTabViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainerTabViewModel.kt @@ -1,5 +1,5 @@ package nl.avisi.structurizr.site.generatr.site.model -data class ContainerTabViewModel(val pageViewModel: SoftwareSystemPageViewModel, val title: String, val url: String) { - val link = LinkViewModel(pageViewModel, title, url) +data class ContainerTabViewModel(val pageViewModel: SoftwareSystemPageViewModel, val title: String, val url: String, val match: Match = Match.EXACT) { + val link = LinkViewModel(pageViewModel, title, url, match) } diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainersCodeTabViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainersCodeTabViewModel.kt new file mode 100644 index 00000000..345acd2e --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainersCodeTabViewModel.kt @@ -0,0 +1,29 @@ +package nl.avisi.structurizr.site.generatr.site.model + +import com.structurizr.model.SoftwareSystem +import nl.avisi.structurizr.site.generatr.hasComponentDiagrams +import nl.avisi.structurizr.site.generatr.hasImageViews +import nl.avisi.structurizr.site.generatr.site.GeneratorContext + +fun SoftwareSystemPageViewModel.createContainersCodeTabViewModel( + generatorContext: GeneratorContext, + softwareSystem: SoftwareSystem, +) = buildList { + softwareSystem + .containers + .sortedBy { it.name } + .filter { container -> + (generatorContext.workspace.hasComponentDiagrams(container) or + generatorContext.workspace.hasImageViews(container.id)) and + container.components.any { component -> generatorContext.workspace.hasImageViews(component.id) } + } + .map { container -> + ContainerTabViewModel( + this@createContainersCodeTabViewModel, + container.name, + SoftwareSystemContainerComponentCodePageViewModel.url(container, container.components.sortedBy { it.name }.firstOrNull { component -> generatorContext.workspace.hasImageViews(component.id) }), + Match.SIBLING + ) + } + .forEach { add(it) } +} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainersTabViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainersComponentTabViewModel.kt similarity index 83% rename from src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainersTabViewModel.kt rename to src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainersComponentTabViewModel.kt index 2e8b4c89..5725e0e3 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainersTabViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainersComponentTabViewModel.kt @@ -5,18 +5,19 @@ import nl.avisi.structurizr.site.generatr.hasComponentDiagrams import nl.avisi.structurizr.site.generatr.hasImageViews import nl.avisi.structurizr.site.generatr.site.GeneratorContext -fun SoftwareSystemPageViewModel.createContainersTabViewModel( +fun SoftwareSystemPageViewModel.createContainersComponentTabViewModel( generatorContext: GeneratorContext, softwareSystem: SoftwareSystem, ) = buildList { softwareSystem .containers + .sortedBy { it.name } .filter { container -> generatorContext.workspace.hasComponentDiagrams(container) or generatorContext.workspace.hasImageViews(container.id) } .map { ContainerTabViewModel( - this@createContainersTabViewModel, + this@createContainersComponentTabViewModel, it.name, SoftwareSystemContainerComponentsPageViewModel.url(it) ) diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/LinkViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/LinkViewModel.kt index dcb1c63f..fc655f49 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/LinkViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/LinkViewModel.kt @@ -14,14 +14,19 @@ data class LinkViewModel( Match.EXACT -> isHrefOfContainingPage Match.CHILD -> isChildHrefOfContainingPage Match.SIBLING -> isSiblingHrefOfContainingPage + Match.SIBLING_CHILD -> isSiblingChildHrefOfContainingPage } private val isHrefOfContainingPage get() = href == pageViewModel.url private val isChildHrefOfContainingPage get() = pageViewModel.url == href || pageViewModel.url.startsWith("$href/") private val isSiblingHrefOfContainingPage get() = pageViewModel.url.trimEnd('/').dropLastWhile { it != '/' } == href.trimEnd('/').dropLastWhile { it != '/' } + private val isSiblingChildHrefOfContainingPage get() = + pageViewModel.url.trimEnd('/').dropLastWhile { it != '/' }.trimEnd('/').dropLastWhile { it != '/' } == + href.trimEnd('/').dropLastWhile { it != '/' }.trimEnd('/').dropLastWhile { it != '/' } } enum class Match { EXACT, CHILD, - SIBLING + SIBLING, + SIBLING_CHILD } diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemCodePageViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemCodePageViewModel.kt deleted file mode 100644 index 6da094e5..00000000 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemCodePageViewModel.kt +++ /dev/null @@ -1,13 +0,0 @@ -package nl.avisi.structurizr.site.generatr.site.model - -import com.structurizr.model.SoftwareSystem -import nl.avisi.structurizr.site.generatr.site.GeneratorContext - -class SoftwareSystemCodePageViewModel(generatorContext: GeneratorContext, softwareSystem: SoftwareSystem) : - SoftwareSystemPageViewModel(generatorContext, softwareSystem, Tab.CODE) { - val images = generatorContext.workspace.views.imageViews - .filter { it.elementId in softwareSystem.containers.flatMap { c -> c.components.map { com -> com.id } } } - .sortedBy { it.key } - .map { ImageViewViewModel(it) } - val visible = images.isNotEmpty() -} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerComponentCodePageViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerComponentCodePageViewModel.kt new file mode 100644 index 00000000..42ddc9e4 --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerComponentCodePageViewModel.kt @@ -0,0 +1,23 @@ +package nl.avisi.structurizr.site.generatr.site.model + +import com.structurizr.model.Container +import com.structurizr.model.Component +import nl.avisi.structurizr.site.generatr.normalize +import nl.avisi.structurizr.site.generatr.site.GeneratorContext + +class SoftwareSystemContainerComponentCodePageViewModel(generatorContext: GeneratorContext, container: Container, component: Component) : + SoftwareSystemPageViewModel(generatorContext, container.softwareSystem, Tab.CODE) { + override val url = url(container, component) + val images = generatorContext.workspace.views.imageViews + .filter { it.elementId in component.id } + .sortedBy { it.key } + .map { ImageViewViewModel(it) } + + val visible = images.isNotEmpty() + val containerTabs = createContainersCodeTabViewModel(generatorContext, container.softwareSystem) + val componentTabs = createComponentsTabViewModel(generatorContext, container) + + companion object { + fun url(container: Container, component: Component?) = "${url(container.softwareSystem, Tab.CODE)}/${container.name.normalize()}/${component?.name?.normalize()}" + } +} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerComponentsPageViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerComponentsPageViewModel.kt index 747faf4d..ba302dd1 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerComponentsPageViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerComponentsPageViewModel.kt @@ -17,7 +17,7 @@ class SoftwareSystemContainerComponentsPageViewModel(generatorContext: Generator .map { ImageViewViewModel(it) } val visible = diagrams.isNotEmpty() or images.isNotEmpty() - val containerTabs = createContainersTabViewModel(generatorContext, container.softwareSystem) + val containerTabs = createContainersComponentTabViewModel(generatorContext, container.softwareSystem) companion object { fun url(container: Container) = "${url(container.softwareSystem, Tab.COMPONENT)}/${container.name.normalize()}" } diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemPageViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemPageViewModel.kt index ec340d93..166e15c2 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemPageViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemPageViewModel.kt @@ -16,7 +16,13 @@ open class SoftwareSystemPageViewModel( Tab.COMPONENT -> LinkViewModel( this@SoftwareSystemPageViewModel, title, - "${url(softwareSystem, tab)}/${softwareSystem.firstContainerName(generatorContext)}", + "${url(softwareSystem, tab)}/${softwareSystem.firstContainer(generatorContext)?.name?.normalize()}", + match + ) + Tab.CODE -> LinkViewModel( + this@SoftwareSystemPageViewModel, + title, + "${url(softwareSystem, tab)}/${softwareSystem.firstContainer(generatorContext)?.name?.normalize()}/${softwareSystem.firstContainer(generatorContext)?.firstComponent(generatorContext)?.name?.normalize()}", match ) else -> LinkViewModel(this@SoftwareSystemPageViewModel, title, url(softwareSystem, tab), match) @@ -59,7 +65,7 @@ open class SoftwareSystemPageViewModel( TabViewModel(Tab.SYSTEM_CONTEXT), TabViewModel(Tab.CONTAINER), TabViewModel(Tab.COMPONENT, Match.SIBLING), - TabViewModel(Tab.CODE), + TabViewModel(Tab.CODE, Match.SIBLING_CHILD), TabViewModel(Tab.DYNAMIC), TabViewModel(Tab.DEPLOYMENT), TabViewModel(Tab.DEPENDENCIES), diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerComponentCodePage.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerComponentCodePage.kt new file mode 100644 index 00000000..6853bf3e --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerComponentCodePage.kt @@ -0,0 +1,36 @@ +package nl.avisi.structurizr.site.generatr.site.views + +import kotlinx.html.HTML +import kotlinx.html.div +import kotlinx.html.li +import kotlinx.html.ul +import nl.avisi.structurizr.site.generatr.site.model.SoftwareSystemContainerComponentCodePageViewModel + +fun HTML.softwareSystemContainerComponentCodePage(viewModel: SoftwareSystemContainerComponentCodePageViewModel) { + if (viewModel.visible) { + softwareSystemPage(viewModel) { + div(classes = "tabs") { + ul(classes = "m-0 is-flex-wrap-wrap is-flex-shrink-1 is-flex-grow-0") { + viewModel.containerTabs + .forEach { + li(classes = if (it.link.active) "is-active" else null) { + link(it.link) + } + } + } + } + div(classes = "tabs is-size-7") { + ul(classes = "m-0 is-flex-wrap-wrap is-flex-shrink-1 is-flex-grow-0") { + viewModel.componentTabs + .forEach { + li(classes = if (it.link.active) "is-active" else null) { + link(it.link) + } + } + } + } + viewModel.images.forEach { image(it) } + } + } else + redirectUpPage() +} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerComponentsPage.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerComponentsPage.kt index f6cedb91..d47b0aab 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerComponentsPage.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerComponentsPage.kt @@ -10,7 +10,7 @@ fun HTML.softwareSystemContainerComponentsPage(viewModel: SoftwareSystemContaine if (viewModel.visible) { softwareSystemPage(viewModel) { div(classes = "tabs") { - ul(classes = "m-0 is-flex-wrap-wrap is flex-shrink-1 is flex-grow-0") { + ul(classes = "m-0 is-flex-wrap-wrap is-flex-shrink-1 is-flex-grow-0") { viewModel.containerTabs .forEach { li(classes = if (it.link.active) "is-active" else null) { diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/softwareSystemCodePage.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/softwareSystemCodePage.kt deleted file mode 100644 index 6aec96b8..00000000 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/softwareSystemCodePage.kt +++ /dev/null @@ -1,14 +0,0 @@ -package nl.avisi.structurizr.site.generatr.site.views - -import kotlinx.html.HTML -import nl.avisi.structurizr.site.generatr.site.model.SoftwareSystemCodePageViewModel - -fun HTML.softwareSystemCodePage(viewModel: SoftwareSystemCodePageViewModel) { - if (viewModel.visible) { - softwareSystemPage(viewModel) { - // TODO: group by containers / components - viewModel.images.forEach { image(it) } - } - } else - redirectUpPage() -} diff --git a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/PlantUmlExporterTest.kt b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/PlantUmlExporterTest.kt index ab1f7063..f2a99487 100644 --- a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/PlantUmlExporterTest.kt +++ b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/PlantUmlExporterTest.kt @@ -173,7 +173,7 @@ class PlantUmlExporterTest { assertThat(diagram.definition.withoutC4HeaderAndFooter()).isEqualTo( """ Container_Boundary("System1.Container1_boundary", "Container 1", ${'$'}tags="") { - Component(System1.Container1.Component1, "Component 1", ${'$'}techn="", ${'$'}descr="", ${'$'}tags="", ${'$'}link="../system-1/code/") + Component(System1.Container1.Component1, "Component 1", ${'$'}techn="", ${'$'}descr="", ${'$'}tags="", ${'$'}link="../system-1/code/container-1/component-1/") Component(System1.Container1.Component2, "Component 2", ${'$'}techn="", ${'$'}descr="", ${'$'}tags="", ${'$'}link="") Component(System1.Container1.Component3, "Component 3", ${'$'}techn="", ${'$'}descr="", ${'$'}tags="", ${'$'}link="") } @@ -191,7 +191,7 @@ class PlantUmlExporterTest { assertThat(diagram.definition.withoutStructurizrHeaderAndFooter()).isEqualTo( """ rectangle "Container 1\n[Container]" <> { - rectangle "==Component 1\n[Component]" <> as System1.Container1.Component1 [[../system-1/code/]] + rectangle "==Component 1\n[Component]" <> as System1.Container1.Component1 [[../system-1/code/container-1/component-1/]] rectangle "==Component 2\n[Component]" <> as System1.Container1.Component2 rectangle "==Component 3\n[Component]" <> as System1.Container1.Component3 } diff --git a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/LinkViewModelTest.kt b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/LinkViewModelTest.kt index 3a9d6097..2745d8cc 100644 --- a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/LinkViewModelTest.kt +++ b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/LinkViewModelTest.kt @@ -78,4 +78,28 @@ class LinkViewModelTest : ViewModelTest() { assertThat(expectInactiveSiblingMatch.active).isFalse() assertThat(expectActiveSiblingMatch.active).isTrue() } + + @ParameterizedTest + @ValueSource(strings = ["/some-page/sibling/child/", "/some-page/other-sibling/other-child/"]) + fun `sibling child links are active when two url paths back matches`(pageHref: String) { + val pageViewModel = pageViewModel(pageHref) + val viewModel = LinkViewModel(pageViewModel, "Some page", "/some-page/another-sibling/another-child", Match.SIBLING_CHILD) + assertThat(viewModel.active).isTrue() + } + + @ParameterizedTest + @ValueSource(strings = ["/some-page/sibling/child/", "/some-page/other-sibling/other-child/"]) + fun `sibling child links are not active when two url paths back doesnt match`(pageHref: String) { + val pageViewModel = pageViewModel(pageHref) + val viewModel = LinkViewModel(pageViewModel, "Some other page", "/some-other-page/not-a-sibling/child/", Match.SIBLING_CHILD) + assertThat(viewModel.active).isFalse() + } + + @Test + fun `sibling child links are only active when two url paths back matches with two url paths back from href`() { + val expectInactiveSiblingChildMatch = LinkViewModel(pageViewModel("/page/one/description"), "Some page", "/some-page/", Match.SIBLING_CHILD) + val expectActiveSiblingChildMatch = LinkViewModel(pageViewModel("/page/one/description"), "Some page", "/page/two/title-screen", Match.SIBLING_CHILD) + assertThat(expectInactiveSiblingChildMatch.active).isFalse() + assertThat(expectActiveSiblingChildMatch.active).isTrue() + } } diff --git a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemCodePageViewModelTest.kt b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemCodePageViewModelTest.kt deleted file mode 100644 index 28e6032a..00000000 --- a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemCodePageViewModelTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -package nl.avisi.structurizr.site.generatr.site.model - -import assertk.assertThat -import assertk.assertions.hasSize -import assertk.assertions.isEqualTo -import assertk.assertions.isFalse -import assertk.assertions.isTrue -import com.structurizr.model.SoftwareSystem -import org.junit.jupiter.api.Test - -class SoftwareSystemCodePageViewModelTest : ViewModelTest() { - private val generatorContext = generatorContext() - private val softwareSystem: SoftwareSystem = generatorContext.workspace.model.addSoftwareSystem("Software system") - private val container = softwareSystem.addContainer("container") - private val component = container.addComponent("component") - private val imageView = createImageView(generatorContext.workspace, component) - - @Test - fun `active tab`() { - val viewModel = SoftwareSystemCodePageViewModel(generatorContext, softwareSystem) - - assertThat(viewModel.tabs.single { it.link.active }.tab) - .isEqualTo(SoftwareSystemPageViewModel.Tab.CODE) - } - - @Test - fun `has image`() { - val viewModel = SoftwareSystemCodePageViewModel(generatorContext, softwareSystem) - - assertThat(viewModel.visible).isTrue() - assertThat(viewModel.images).hasSize(1) - assertThat(viewModel.images.single().imageView).isEqualTo(imageView) - } - - @Test - fun `hidden view`() { - val viewModel = SoftwareSystemCodePageViewModel( - generatorContext, - generatorContext.workspace.model.addSoftwareSystem("Software system 2").apply { - addContainer("container").apply { - addComponent("component") - } - } - ) - - assertThat(viewModel.visible).isFalse() - } -} diff --git a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerComponentCodePageViewModelTest.kt b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerComponentCodePageViewModelTest.kt new file mode 100644 index 00000000..d02e96db --- /dev/null +++ b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerComponentCodePageViewModelTest.kt @@ -0,0 +1,110 @@ +package nl.avisi.structurizr.site.generatr.site.model + +import assertk.assertThat +import assertk.assertions.hasSize +import assertk.assertions.isEqualTo +import assertk.assertions.isFalse +import assertk.assertions.isTrue +import com.structurizr.model.Container +import com.structurizr.model.SoftwareSystem +import nl.avisi.structurizr.site.generatr.normalize +import org.junit.jupiter.api.Test + +class SoftwareSystemContainerComponentCodePageViewModelTest : ViewModelTest() { + private val generatorContext = generatorContext() + private val softwareSystem: SoftwareSystem = generatorContext.workspace.model + .addSoftwareSystem("Software system").also { + val backend = it.addContainer("Backend") + val frontend = it.addContainer("Frontend") + generatorContext.workspace.views.createComponentView(backend, "component-1-backend", "Component view 1 - Backend") + generatorContext.workspace.views.createComponentView(backend, "component-2-backend", "Component view 2 - Backend") + + generatorContext.workspace.views.createComponentView(frontend, "component-1-frontend", "Component view 1 - Frontend") + generatorContext.workspace.views.createComponentView(frontend, "component-2-frontend", "Component view 2 - Frontend") + } + private val backendContainer: Container = softwareSystem.containers.elementAt(0) + private val frontendContainer: Container = softwareSystem.containers.elementAt(1) + private val backendComponent = backendContainer.addComponent("Backend Component") + private val backendComponent2 = backendContainer.addComponent("Java Application") + private val frontendComponent = frontendContainer.addComponent("Frontend Component") + private val backendImageView = createImageView(generatorContext.workspace, backendComponent) + private val backendImageView2 = createImageView(generatorContext.workspace, backendComponent2) + private val frontendImageView = createImageView(generatorContext.workspace, frontendComponent) + + @Test + fun `active tab`() { + val viewModel = SoftwareSystemContainerComponentCodePageViewModel(generatorContext, backendContainer, backendComponent) + + assertThat(viewModel.tabs.single { it.link.active }.tab) + .isEqualTo(SoftwareSystemPageViewModel.Tab.CODE) + } + + @Test + fun `container tabs`() { + val viewModel = SoftwareSystemContainerComponentCodePageViewModel(generatorContext, backendContainer, backendComponent) + val containerTabList = listOf( + ContainerTabViewModel(viewModel, "Backend", "/software-system/code/backend/backend-component", Match.SIBLING), + ContainerTabViewModel(viewModel, "Frontend", "/software-system/code/frontend/frontend-component", Match.SIBLING) + ) + assertThat(viewModel.containerTabs.elementAtOrNull(0)).isEqualTo(containerTabList.elementAt(0)) + assertThat(viewModel.containerTabs.elementAtOrNull(1)).isEqualTo(containerTabList.elementAt(1)) + } + + @Test + fun `component tabs`() { + val viewModel = SoftwareSystemContainerComponentCodePageViewModel(generatorContext, backendContainer, backendComponent) + val componentTabList = listOf( + ComponentTabViewModel(viewModel, "Backend Component", "/software-system/code/backend/backend-component"), + ComponentTabViewModel(viewModel, "Java Application", "/software-system/code/backend/java-application") + ) + assertThat(viewModel.componentTabs.elementAtOrNull(0)).isEqualTo(componentTabList.elementAt(0)) + assertThat(viewModel.componentTabs.elementAtOrNull(1)).isEqualTo(componentTabList.elementAt(1)) + } + + @Test + fun url() { + var viewModel = SoftwareSystemContainerComponentCodePageViewModel(generatorContext, backendContainer, backendComponent) + assertThat(SoftwareSystemContainerComponentCodePageViewModel.url(backendContainer, backendComponent)) + .isEqualTo("/${softwareSystem.name.normalize()}/code/${backendContainer.name.normalize()}/${backendComponent.name.normalize()}") + assertThat(viewModel.url) + .isEqualTo(SoftwareSystemContainerComponentCodePageViewModel.url(backendContainer, backendComponent)) + + viewModel = SoftwareSystemContainerComponentCodePageViewModel(generatorContext, frontendContainer, frontendComponent) + assertThat(SoftwareSystemContainerComponentCodePageViewModel.url(frontendContainer, frontendComponent)) + .isEqualTo("/${softwareSystem.name.normalize()}/code/${frontendContainer.name.normalize()}/${frontendComponent.name.normalize()}") + assertThat(viewModel.url) + .isEqualTo(SoftwareSystemContainerComponentCodePageViewModel.url(frontendContainer, frontendComponent)) + } + + @Test + fun `has image`() { + var viewModel = SoftwareSystemContainerComponentCodePageViewModel(generatorContext, backendContainer, backendComponent) + + assertThat(viewModel.visible).isTrue() + assertThat(viewModel.images).hasSize(1) + assertThat(viewModel.images.single().imageView).isEqualTo(backendImageView) + + viewModel = SoftwareSystemContainerComponentCodePageViewModel(generatorContext, backendContainer, backendComponent2) + + assertThat(viewModel.visible).isTrue() + assertThat(viewModel.images).hasSize(1) + assertThat(viewModel.images.single().imageView).isEqualTo(backendImageView2) + + viewModel = SoftwareSystemContainerComponentCodePageViewModel(generatorContext, frontendContainer, frontendComponent) + + assertThat(viewModel.visible).isTrue() + assertThat(viewModel.images).hasSize(1) + assertThat(viewModel.images.single().imageView).isEqualTo(frontendImageView) + } + + @Test + fun `hidden view`() { + val viewModel = SoftwareSystemContainerComponentCodePageViewModel( + generatorContext, + softwareSystem.addContainer("Container"), + backendContainer.addComponent("Component") + ) + + assertThat(viewModel.visible).isFalse() + } +} diff --git a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemPageViewModelTest.kt b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemPageViewModelTest.kt index 73db47d5..a350f3c5 100644 --- a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemPageViewModelTest.kt +++ b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemPageViewModelTest.kt @@ -85,7 +85,7 @@ class SoftwareSystemPageViewModelTest : ViewModelTest() { @TestFactory fun `active tab`() = Tab.entries - .filter { it != Tab.COMPONENT } // Component link is dynamic + .filter { it != Tab.COMPONENT && it != Tab.CODE } // Component & code links are dynamic .map { tab -> DynamicTest.dynamicTest("active tab - $tab") { val generatorContext = generatorContext() @@ -152,6 +152,20 @@ class SoftwareSystemPageViewModelTest : ViewModelTest() { assertThat(getTab(viewModel, Tab.COMPONENT).visible).isTrue() } + @Test + fun `code views tab only visible when component diagrams available and component diagram has image view`() { + val generatorContext = generatorContext() + val softwareSystem = generatorContext.workspace.model.addSoftwareSystem("Some software system") + val container = softwareSystem.addContainer("Backend") + val viewModel = SoftwareSystemPageViewModel(generatorContext, softwareSystem, Tab.HOME) + + assertThat(getTab(viewModel, Tab.CODE).visible).isFalse() + val component = container.addComponent("Backend Component") + generatorContext.workspace.views.createComponentView(container, "component", "description") + createImageView(generatorContext.workspace, component) + assertThat(getTab(viewModel, Tab.CODE).visible).isTrue() + } + @Test fun `dynamic views tab only visible when dynamic diagrams available`() { val generatorContext = generatorContext()