diff --git a/docs/example/internet-banking-system/adr/0001-record-architecture-decisions.md b/docs/example/internet-banking-system/adr/0001-record-architecture-decisions.md index 63f18d4d..498288fe 100644 --- a/docs/example/internet-banking-system/adr/0001-record-architecture-decisions.md +++ b/docs/example/internet-banking-system/adr/0001-record-architecture-decisions.md @@ -1,4 +1,4 @@ -# 1. Record architecture decisions +# 1. Record Internet Banking System architecture decisions Date: 2022-06-21 diff --git a/docs/example/internet-banking-system/api-application/adr/0001-record-architecture-decisions.md b/docs/example/internet-banking-system/api-application/adr/0001-record-architecture-decisions.md new file mode 100644 index 00000000..d9b55b2d --- /dev/null +++ b/docs/example/internet-banking-system/api-application/adr/0001-record-architecture-decisions.md @@ -0,0 +1,19 @@ +# 1. Record API Application architecture decision records + +Date: 2022-06-21 + +## Status + +Accepted + +## Context + +We need to record the architectural decisions made on this project. + +## Decision + +We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). + +## Consequences + +See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools). diff --git a/docs/example/workspace.dsl b/docs/example/workspace.dsl index cddc7d45..cd8c35d2 100644 --- a/docs/example/workspace.dsl +++ b/docs/example/workspace.dsl @@ -42,6 +42,7 @@ workspace "Big Bank plc" "This is an example workspace to illustrate the key fea mobileApp = container "Mobile App" "Provides a limited subset of the Internet banking functionality to customers via their mobile device." "Xamarin" "Mobile App" webApplication = container "Web Application" "Delivers the static content and the Internet banking single page application." "Java and Spring MVC" apiApplication = container "API Application" "Provides Internet banking functionality via a JSON/HTTPS API." "Java and Spring MVC" { + !adrs internet-banking-system/api-application/adr signinController = component "Sign In Controller" "Allows users to sign in to the Internet Banking System." "Spring MVC Rest Controller" accountsSummaryController = component "Accounts Summary Controller" "Provides customers with a summary of their bank accounts." "Spring MVC Rest Controller" resetPasswordController = component "Reset Password Controller" "Allows users to reset their passwords with a single use URL." "Spring MVC Rest Controller" 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 9d101969..dc7dec3d 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/StructurizrUtilities.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/StructurizrUtilities.kt @@ -23,8 +23,12 @@ val SoftwareSystem.includedProperties fun SoftwareSystem.hasDecisions() = documentation.decisions.isNotEmpty() +fun SoftwareSystem.hasContainerDecisions() = containers.any { it.hasDecisions() } + fun SoftwareSystem.hasDocumentationSections() = documentation.sections.size >= 2 +fun Container.hasDecisions() = documentation.decisions.isNotEmpty() + fun ViewSet.hasSystemContextViews(softwareSystem: SoftwareSystem) = systemContextViews.any { it.softwareSystem == softwareSystem } 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 c4578816..b9729244 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 @@ -137,6 +137,15 @@ private fun generateHtmlFiles(context: GeneratorContext, branchDir: File) { add { writeHtmlFile(branchDir, SoftwareSystemDecisionPageViewModel(context, it, decision)) } } + it.containers + .filter { container -> container.documentation.decisions.isNotEmpty() } + .forEach { container -> + add { writeHtmlFile(branchDir, SoftwareSystemContainerDecisionsPageViewModel(context, container)) } + container.documentation.decisions.forEach { decision -> + add { writeHtmlFile(branchDir, SoftwareSystemContainerDecisionPageViewModel(context, container, decision)) } + } + } + it.documentation.sections.filter { section -> section.order != 1 }.forEach { section -> add { writeHtmlFile(branchDir, SoftwareSystemSectionPageViewModel(context, it, section)) } } @@ -157,6 +166,8 @@ private fun writeHtmlFile(exportDir: File, viewModel: PageViewModel) { is SoftwareSystemHomePageViewModel -> softwareSystemHomePage(viewModel) is SoftwareSystemContextPageViewModel -> softwareSystemContextPage(viewModel) is SoftwareSystemContainerPageViewModel -> softwareSystemContainerPage(viewModel) + is SoftwareSystemContainerDecisionPageViewModel -> softwareSystemContainerDecisionPage(viewModel) + is SoftwareSystemContainerDecisionsPageViewModel -> softwareSystemContainerDecisionsPage(viewModel) is SoftwareSystemComponentPageViewModel -> softwareSystemComponentPage(viewModel) is SoftwareSystemDeploymentPageViewModel -> softwareSystemDeploymentPage(viewModel) is SoftwareSystemDependenciesPageViewModel -> softwareSystemDependenciesPage(viewModel) diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainerDecisionsTableViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainerDecisionsTableViewModel.kt new file mode 100644 index 00000000..ed02910d --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainerDecisionsTableViewModel.kt @@ -0,0 +1,18 @@ +package nl.avisi.structurizr.site.generatr.site.model + +import com.structurizr.model.Container +import nl.avisi.structurizr.site.generatr.hasDecisions + +fun PageViewModel.createContainerDecisionsTableViewModel(containers: Collection, hrefFactory: (Container) -> String) = + TableViewModel.create { + headerRow(headerCell("#"), headerCell("Container Decisions")) + containers + .sortedBy { it.name } + .filter { it.hasDecisions() } + .forEachIndexed { index, container -> + bodyRow( + cellWithIndex((index+1).toString()), + cellWithLink(this@createContainerDecisionsTableViewModel, container.name, hrefFactory(container)) + ) + } + } diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/DecisionTabViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/DecisionTabViewModel.kt new file mode 100644 index 00000000..51a99498 --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/DecisionTabViewModel.kt @@ -0,0 +1,32 @@ +package nl.avisi.structurizr.site.generatr.site.model + +import com.structurizr.model.SoftwareSystem +import nl.avisi.structurizr.site.generatr.hasDecisions + +data class DecisionTabViewModel(val pageViewModel: SoftwareSystemPageViewModel, val title: String, val url: String) { + val link = LinkViewModel(pageViewModel, title, url, true) +} + +fun SoftwareSystemPageViewModel.createDecisionsTabViewModel(softwareSystem: SoftwareSystem, tab: SoftwareSystemPageViewModel.Tab): List { + val tabs = buildList { + if (softwareSystem.hasDecisions()) { + add(DecisionTabViewModel( + this@createDecisionsTabViewModel, + "System", + SoftwareSystemPageViewModel.url(softwareSystem, tab) + )) + } + softwareSystem + .containers + .filter { it.hasDecisions() } + .map { + DecisionTabViewModel( + this@createDecisionsTabViewModel, + it.name, + SoftwareSystemContainerDecisionsPageViewModel.url(it) + ) + } + .forEach { add(it) } + } + return tabs +} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerDecisionPageViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerDecisionPageViewModel.kt new file mode 100644 index 00000000..1cb49aa9 --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerDecisionPageViewModel.kt @@ -0,0 +1,19 @@ +package nl.avisi.structurizr.site.generatr.site.model + +import com.structurizr.documentation.Decision +import com.structurizr.model.Container +import nl.avisi.structurizr.site.generatr.normalize +import nl.avisi.structurizr.site.generatr.site.GeneratorContext + +class SoftwareSystemContainerDecisionPageViewModel( + generatorContext: GeneratorContext, container: Container, decision: Decision +) : SoftwareSystemPageViewModel(generatorContext, container.softwareSystem, Tab.DECISIONS) { + override val url = url(container, decision) + + val content = markdownToHtml(this, decision.content, generatorContext.svgFactory) + + companion object { + fun url(container: Container, decision: Decision) = + "${url(container.softwareSystem, Tab.DECISIONS)}/${container.name.normalize()}/${decision.id}" + } +} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerDecisionsPageViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerDecisionsPageViewModel.kt new file mode 100644 index 00000000..8a98dd6a --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerDecisionsPageViewModel.kt @@ -0,0 +1,22 @@ +package nl.avisi.structurizr.site.generatr.site.model + +import com.structurizr.model.Container +import nl.avisi.structurizr.site.generatr.hasDecisions +import nl.avisi.structurizr.site.generatr.normalize +import nl.avisi.structurizr.site.generatr.site.GeneratorContext + +class SoftwareSystemContainerDecisionsPageViewModel(generatorContext: GeneratorContext, container: Container) : + SoftwareSystemPageViewModel(generatorContext, container.softwareSystem, Tab.DECISIONS) { + override val url = url(container) + val decisionsTable = createDecisionsTableViewModel(container.documentation.decisions) { + "$url/${it.id}" + } + + val visible = container.hasDecisions() + val decisionTabs = createDecisionsTabViewModel(container.softwareSystem, Tab.DECISIONS) + + companion object { + fun url(container: Container) = + "${url(container.softwareSystem, Tab.DECISIONS)}/${container.name.normalize()}" + } +} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemDecisionsPageViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemDecisionsPageViewModel.kt index 34e393c2..70158f09 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemDecisionsPageViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemDecisionsPageViewModel.kt @@ -1,13 +1,23 @@ package nl.avisi.structurizr.site.generatr.site.model import com.structurizr.model.SoftwareSystem +import nl.avisi.structurizr.site.generatr.hasContainerDecisions import nl.avisi.structurizr.site.generatr.hasDecisions import nl.avisi.structurizr.site.generatr.site.GeneratorContext class SoftwareSystemDecisionsPageViewModel(generatorContext: GeneratorContext, softwareSystem: SoftwareSystem) : SoftwareSystemPageViewModel(generatorContext, softwareSystem, Tab.DECISIONS) { + val decisionsTable = createDecisionsTableViewModel(softwareSystem.documentation.decisions) { "$url/${it.id}" } - val visible = softwareSystem.hasDecisions() + + private val containerDecisionsVisible = softwareSystem.hasContainerDecisions() + val softwareSystemDecisionsVisible = softwareSystem.hasDecisions() + + val visible = softwareSystemDecisionsVisible or containerDecisionsVisible + val onlyContainersDecisionsVisible = !softwareSystemDecisionsVisible and containerDecisionsVisible + + val decisionTabs = createDecisionsTabViewModel(softwareSystem, Tab.DECISIONS) + } 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 23dc1882..f318c3fb 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 @@ -1,13 +1,7 @@ package nl.avisi.structurizr.site.generatr.site.model import com.structurizr.model.SoftwareSystem -import nl.avisi.structurizr.site.generatr.hasComponentViews -import nl.avisi.structurizr.site.generatr.hasContainerViews -import nl.avisi.structurizr.site.generatr.hasDecisions -import nl.avisi.structurizr.site.generatr.hasDeploymentViews -import nl.avisi.structurizr.site.generatr.hasDocumentationSections -import nl.avisi.structurizr.site.generatr.hasSystemContextViews -import nl.avisi.structurizr.site.generatr.normalize +import nl.avisi.structurizr.site.generatr.* import nl.avisi.structurizr.site.generatr.site.GeneratorContext open class SoftwareSystemPageViewModel( @@ -40,7 +34,7 @@ open class SoftwareSystemPageViewModel( Tab.CONTAINER -> generatorContext.workspace.views.hasContainerViews(softwareSystem) Tab.COMPONENT -> generatorContext.workspace.views.hasComponentViews(softwareSystem) Tab.DEPLOYMENT -> generatorContext.workspace.views.hasDeploymentViews(softwareSystem) - Tab.DECISIONS -> softwareSystem.hasDecisions() + Tab.DECISIONS -> softwareSystem.hasDecisions() or softwareSystem.hasContainerDecisions() Tab.SECTIONS -> softwareSystem.hasDocumentationSections() } } diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemSectionPageViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemSectionPageViewModel.kt index 49738edf..9553a81e 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemSectionPageViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemSectionPageViewModel.kt @@ -6,7 +6,7 @@ import nl.avisi.structurizr.site.generatr.site.GeneratorContext class SoftwareSystemSectionPageViewModel( generatorContext: GeneratorContext, softwareSystem: SoftwareSystem, section: Section -) : SoftwareSystemPageViewModel(generatorContext, softwareSystem, Tab.DECISIONS) { +) : SoftwareSystemPageViewModel(generatorContext, softwareSystem, Tab.SECTIONS) { override val url = url(softwareSystem, section) val content = markdownToHtml(this, section.content, generatorContext.svgFactory) diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/RedirectRelative.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/RedirectRelative.kt new file mode 100644 index 00000000..8bc6a04f --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/RedirectRelative.kt @@ -0,0 +1,19 @@ +package nl.avisi.structurizr.site.generatr.site.views + +import kotlinx.html.HTML +import kotlinx.html.body +import kotlinx.html.head +import kotlinx.html.meta +import kotlinx.html.title + +fun HTML.redirectRelative(appendUrl: String) { + attributes["lang"] = "en" + head { + meta { + httpEquiv = "refresh" + content = "0; url=./$appendUrl" + } + title { +"Structurizr site generatr" } + } + body() +} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerDecisionPage.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerDecisionPage.kt new file mode 100644 index 00000000..d4244495 --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerDecisionPage.kt @@ -0,0 +1,10 @@ +package nl.avisi.structurizr.site.generatr.site.views + +import kotlinx.html.HTML +import nl.avisi.structurizr.site.generatr.site.model.SoftwareSystemContainerDecisionPageViewModel + +fun HTML.softwareSystemContainerDecisionPage(viewModel: SoftwareSystemContainerDecisionPageViewModel) { + softwareSystemPage(viewModel) { + rawHtml(viewModel.content) + } +} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerDecisionsPage.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerDecisionsPage.kt new file mode 100644 index 00000000..a9a98a28 --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemContainerDecisionsPage.kt @@ -0,0 +1,26 @@ +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.SoftwareSystemContainerDecisionsPageViewModel + +fun HTML.softwareSystemContainerDecisionsPage(viewModel: SoftwareSystemContainerDecisionsPageViewModel) { + if (viewModel.visible) + softwareSystemPage(viewModel) { + div(classes = "tabs") { + ul(classes = "m-0") { + viewModel.decisionTabs + .forEach { + li(classes = if (it.link.active) "is-active" else null) { + link(it.link) + } + } + } + } + table(viewModel.decisionsTable) + } + else + redirectUpPage() +} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemDecisionsPage.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemDecisionsPage.kt index a00e8fc0..6c23fbf4 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemDecisionsPage.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/SoftwareSystemDecisionsPage.kt @@ -1,13 +1,34 @@ 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.SoftwareSystemDecisionsPageViewModel fun HTML.softwareSystemDecisionsPage(viewModel: SoftwareSystemDecisionsPageViewModel) { - if (viewModel.visible) + if (viewModel.onlyContainersDecisionsVisible) { + redirectRelative( + viewModel.decisionTabs.first().link.relativeHref + ) + } + else if (viewModel.visible) { softwareSystemPage(viewModel) { - table(viewModel.decisionsTable) + div(classes = "tabs") { + ul(classes = "m-0") { + viewModel.decisionTabs + .forEach { + li(classes = if (it.link.active) "is-active" else null) { + link(it.link) + } + } + } + } + if (viewModel.softwareSystemDecisionsVisible) { + table(viewModel.decisionsTable) + } } + } else redirectUpPage() } diff --git a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainerDecisionsTableViewModelTest.kt b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainerDecisionsTableViewModelTest.kt new file mode 100644 index 00000000..3194894d --- /dev/null +++ b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/ContainerDecisionsTableViewModelTest.kt @@ -0,0 +1,47 @@ +package nl.avisi.structurizr.site.generatr.site.model + +import assertk.assertThat +import assertk.assertions.isEqualTo +import kotlin.test.Test + +class ContainerDecisionsTableViewModelTest : ViewModelTest() { + + @Test + fun `no container with decisions available`() { + assertThat(pageViewModel().createContainerDecisionsTableViewModel(emptySet()) { "href" }) + .isEqualTo( + TableViewModel.create { + containersTableHeaderRow() + } + ) + } + + @Test + fun `many containers shown if they have decisions`() { + val containers = generatorContext().workspace.model.addSoftwareSystem("Mock").also { + it.addContainer("Web Application") + it.addContainer("API Application") + .documentation.addDecision(createDecision("1","API Decision")) + it.addContainer("Mobile Application") + .documentation.addDecision(createDecision("1", "Mobile Decision")) + }.containers + val pageViewModel = pageViewModel() + assertThat(pageViewModel.createContainerDecisionsTableViewModel(containers) { it.name }).isEqualTo( + TableViewModel.create { + containersTableHeaderRow() + bodyRow( + cellWithIndex("1"), + cellWithLink(pageViewModel, "API Application", "API Application"), + ) + bodyRow( + cellWithIndex("2"), + cellWithLink(pageViewModel, "Mobile Application", "Mobile Application") + ) + } + ) + } + + private fun TableViewModel.TableViewInitializerContext.containersTableHeaderRow() { + headerRow(headerCell("#"), headerCell("Container Decisions")) + } +} diff --git a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/DecisionTabViewModelTest.kt b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/DecisionTabViewModelTest.kt new file mode 100644 index 00000000..417ba996 --- /dev/null +++ b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/DecisionTabViewModelTest.kt @@ -0,0 +1,74 @@ +package nl.avisi.structurizr.site.generatr.site.model + +import assertk.assertThat +import assertk.assertions.isEqualTo +import kotlin.test.Test + +class DecisionTabViewModelTest : ViewModelTest() { + + @Test + fun `no decision tabs`() { + val context = generatorContext() + val softwareSystem = context.workspace.model.addSoftwareSystem("Software system") + + val softwareSystemPageViewModel = SoftwareSystemPageViewModel(context, softwareSystem, SoftwareSystemPageViewModel.Tab.DECISIONS) + val decisionTabViewModel = softwareSystemPageViewModel.createDecisionsTabViewModel(softwareSystem, SoftwareSystemPageViewModel.Tab.DECISIONS) + + assertThat(decisionTabViewModel.size).isEqualTo(0) + } + + @Test + fun `software system decision tab available`() { + val context = generatorContext() + val softwareSystem = context.workspace.model.addSoftwareSystem("Software system") + softwareSystem.documentation.addDecision(createDecision("1", "Accepted")) + + val softwareSystemPageViewModel = SoftwareSystemPageViewModel(context, softwareSystem, SoftwareSystemPageViewModel.Tab.DECISIONS) + val decisionTabViewModel = softwareSystemPageViewModel.createDecisionsTabViewModel(softwareSystem, SoftwareSystemPageViewModel.Tab.DECISIONS) + + assertThat(decisionTabViewModel.size).isEqualTo(1) + assertThat(decisionTabViewModel.first().title).isEqualTo("System") + } + + @Test + fun `container decision tab available `() { + val context = generatorContext() + val softwareSystem = context.workspace.model.addSoftwareSystem("Software system") + softwareSystem.addContainer("Some Container").documentation.addDecision(createDecision("2", "Proposed")) + + val softwareSystemPageViewModel = SoftwareSystemPageViewModel(context, softwareSystem, SoftwareSystemPageViewModel.Tab.DECISIONS) + val decisionTabViewModel = softwareSystemPageViewModel.createDecisionsTabViewModel(softwareSystem, SoftwareSystemPageViewModel.Tab.DECISIONS) + + assertThat(decisionTabViewModel.size).isEqualTo(1) + assertThat(decisionTabViewModel.first().title).isEqualTo("Some Container") + + } + @Test + fun `container & system decision tabs available`() { + val context = generatorContext() + val softwareSystem = context.workspace.model.addSoftwareSystem("Software system") + softwareSystem.documentation.addDecision(createDecision("1", "Accepted")) + softwareSystem.addContainer("Some Container").documentation.addDecision(createDecision("2", "Proposed")) + + val softwareSystemPageViewModel = SoftwareSystemPageViewModel(context, softwareSystem, SoftwareSystemPageViewModel.Tab.DECISIONS) + val decisionTabViewModel = softwareSystemPageViewModel.createDecisionsTabViewModel(softwareSystem, SoftwareSystemPageViewModel.Tab.DECISIONS) + + assertThat(decisionTabViewModel.size).isEqualTo(2) + assertThat(decisionTabViewModel.first().title).isEqualTo("System") + assertThat(decisionTabViewModel.last().title).isEqualTo("Some Container") + } + + @Test + fun `check container decisions tabs are filtered`() { + val context = generatorContext() + val softwareSystem = context.workspace.model.addSoftwareSystem("Software system") + softwareSystem.addContainer("Some Container").documentation.addDecision(createDecision("2", "Proposed")) + softwareSystem.addContainer("Another Container") + + val softwareSystemPageViewModel = SoftwareSystemPageViewModel(context, softwareSystem, SoftwareSystemPageViewModel.Tab.DECISIONS) + val decisionTabViewModel = softwareSystemPageViewModel.createDecisionsTabViewModel(softwareSystem, SoftwareSystemPageViewModel.Tab.DECISIONS) + + assertThat(decisionTabViewModel.size).isEqualTo(1) + assertThat(decisionTabViewModel.last().title).isEqualTo("Some Container") + } +} diff --git a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerDecisionPageViewModelTest.kt b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerDecisionPageViewModelTest.kt new file mode 100644 index 00000000..2524b365 --- /dev/null +++ b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerDecisionPageViewModelTest.kt @@ -0,0 +1,40 @@ +package nl.avisi.structurizr.site.generatr.site.model + +import assertk.assertThat +import assertk.assertions.isEqualTo +import nl.avisi.structurizr.site.generatr.normalize +import kotlin.test.Test + +class SoftwareSystemContainerDecisionPageViewModelTest : ViewModelTest() { + private val generatorContext = generatorContext() + private val softwareSystem = generatorContext.workspace.model.addSoftwareSystem("Software System").also { + it.addContainer("API Application") + } + private val container = softwareSystem.containers.first() + @Test + fun url() { + val decision = createDecision() + val viewModel = SoftwareSystemContainerDecisionPageViewModel(generatorContext, container, decision) + + assertThat(SoftwareSystemContainerDecisionPageViewModel.url(container, decision)) + .isEqualTo("/${softwareSystem.name.normalize()}/decisions/${container.name.normalize()}/${decision.id}") + assertThat(viewModel.url) + .isEqualTo(SoftwareSystemContainerDecisionPageViewModel.url(container, decision)) + } + + @Test + fun `active tab`() { + val viewModel = SoftwareSystemContainerDecisionPageViewModel(generatorContext, container, createDecision()) + + assertThat(viewModel.tabs.single { it.link.active }.tab) + .isEqualTo(SoftwareSystemPageViewModel.Tab.DECISIONS) + } + + @Test + fun content() { + val decision = createDecision() + val viewModel = SoftwareSystemContainerDecisionPageViewModel(generatorContext, container, decision) + + assertThat(viewModel.content).isEqualTo(markdownToHtml(viewModel, decision.content, svgFactory)) + } +} diff --git a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerDecisionsPageViewModelTest.kt b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerDecisionsPageViewModelTest.kt new file mode 100644 index 00000000..cd16a069 --- /dev/null +++ b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/SoftwareSystemContainerDecisionsPageViewModelTest.kt @@ -0,0 +1,45 @@ +package nl.avisi.structurizr.site.generatr.site.model + +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isFalse +import nl.avisi.structurizr.site.generatr.normalize +import kotlin.test.Test + +class SoftwareSystemContainerDecisionsPageViewModelTest : ViewModelTest() { + private val generatorContext = generatorContext() + private val softwareSystem = generatorContext.workspace.model.addSoftwareSystem("Software system").also { + it.addContainer("API Application") + } + private val container = softwareSystem.containers.first() + @Test + fun `active tab`() { + val viewModel = SoftwareSystemContainerDecisionsPageViewModel(generatorContext, container) + assertThat(viewModel.tabs.single { it.link.active }.tab) + .isEqualTo(SoftwareSystemPageViewModel.Tab.DECISIONS) + } + + @Test + fun `decisions table`() { + container.documentation.addDecision(createDecision("1", "Accepted")) + + val viewModel = SoftwareSystemContainerDecisionsPageViewModel(generatorContext, container) + + assertThat(viewModel.decisionsTable) + .isEqualTo( + viewModel.createDecisionsTableViewModel(container.documentation.decisions) { + "/${softwareSystem.name.normalize()}/decisions/${container.name.normalize()}/1" + } + ) + } + + @Test + fun `hidden view`() { + val viewModel = SoftwareSystemContainerDecisionsPageViewModel( + generatorContext, + softwareSystem.addContainer("Container 2") + ) + + 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 f99d96eb..174b46e1 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 @@ -158,7 +158,7 @@ class SoftwareSystemPageViewModelTest : ViewModelTest() { } @Test - fun `decisions views tab only visible when decisions available`() { + fun `decisions views tab visible when software system decisions available`() { val generatorContext = generatorContext() val softwareSystem = generatorContext.workspace.model.addSoftwareSystem("Some software system") val viewModel = SoftwareSystemPageViewModel(generatorContext, softwareSystem, Tab.HOME) @@ -168,6 +168,29 @@ class SoftwareSystemPageViewModelTest : ViewModelTest() { assertThat(getTab(viewModel, Tab.DECISIONS).visible).isTrue() } + @Test + fun `decisions views tab visible when container decisions available in software system`() { + val generatorContext = generatorContext() + val softwareSystem = generatorContext.workspace.model.addSoftwareSystem("Some software system") + val viewModel = SoftwareSystemPageViewModel(generatorContext, softwareSystem, Tab.HOME) + + assertThat(getTab(viewModel, Tab.DECISIONS).visible).isFalse() + softwareSystem.addContainer("Some Container").documentation.addDecision(createDecision("2", "Proposed")) + assertThat(getTab(viewModel, Tab.DECISIONS).visible).isTrue() + } + + @Test + fun `decisions views tab visible when container & software system decisions are available`() { + val generatorContext = generatorContext() + val softwareSystem = generatorContext.workspace.model.addSoftwareSystem("Some software system") + val viewModel = SoftwareSystemPageViewModel(generatorContext, softwareSystem, Tab.HOME) + + assertThat(getTab(viewModel, Tab.DECISIONS).visible).isFalse() + softwareSystem.addContainer("Some Container").documentation.addDecision(createDecision("2", "Proposed")) + softwareSystem.documentation.addDecision(createDecision("1", "Proposed")) + assertThat(getTab(viewModel, Tab.DECISIONS).visible).isTrue() + } + @Test fun `sections views tab only visible when two or more sections available`() { val generatorContext = generatorContext()