From d9f30c4340ff9a4765c741c9976908597a803df7 Mon Sep 17 00:00:00 2001 From: Alexander Linne Date: Fri, 25 Sep 2026 12:06:29 +0200 Subject: [PATCH 1/4] test: pin PlantUML file builder output with snapshots The dependency and special-character tests assembled their expected diagrams from string literals joined with Environment.NewLine, which hides the shape of the output. Record them as snapshots instead. Also pin how hand-built slices render today: slices sharing parent packages, a package-only slice with a colour, flat slices next to nested ones, C4-style boundaries and a nested slice with a hyperlink. These cover more than the fixture assembly does (several root packages, a component next to a deeper package), and show every shared package being re-opened once per slice and the hyperlink of a nested slice landing after its closing braces. Signed-off-by: Alexander Linne --- .../PlantUml/PlantUmlFileBuilderTest.cs | 154 +++++++++--------- ...st.BuildUmlByDependenciesTest.verified.txt | 10 ++ ...ObjectsWithNoDependenciesTest.verified.txt | 12 ++ ...esSharingParentBoundariesTest.verified.txt | 19 +++ ...tSlicesNextToNestedSlicesTest.verified.txt | 17 ++ ....NestedSliceWithHyperlinkTest.verified.txt | 13 ++ ...PackageOnlySliceWithColorTest.verified.txt | 18 ++ ...icesSharingParentPackagesTest.verified.txt | 31 ++++ ...haractersInComponentNamesTest.verified.txt | 12 ++ 9 files changed, 205 insertions(+), 81 deletions(-) create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.BuildUmlByDependenciesTest.verified.txt create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.BuildUmlByDependenciesWithObjectsWithNoDependenciesTest.verified.txt create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSlicesSharingParentBoundariesTest.verified.txt create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.FlatSlicesNextToNestedSlicesTest.verified.txt create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.NestedSliceWithHyperlinkTest.verified.txt create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.PackageOnlySliceWithColorTest.verified.txt create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SlicesSharingParentPackagesTest.verified.txt create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SpecialCharactersInComponentNamesTest.verified.txt diff --git a/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs b/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs index feae85002..a1991ef8e 100644 --- a/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs +++ b/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using ArchUnitNET.Domain; using ArchUnitNET.Domain.Extensions; using ArchUnitNET.Domain.PlantUml.Exceptions; using ArchUnitNET.Domain.PlantUml.Export; using ArchUnitNET.Fluent.Slices; using ArchUnitNET.Loader; +using VerifyXunit; using Xunit; namespace ArchUnitNETTests.Domain.PlantUml @@ -66,66 +68,16 @@ public void BuildUmlBySlicesTest() } [Fact] - public void BuildUmlByDependenciesTest() + public Task BuildUmlByDependenciesTest() { - var builder = new PlantUmlFileBuilder().WithElements(Dependencies); - var uml = builder.AsString(); - Assert.NotEmpty(uml); - - var expectedUml = - "@startuml" - + Environment.NewLine - + Environment.NewLine - + "!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml" - + Environment.NewLine - + Environment.NewLine - + "HIDE_STEREOTYPE()" - + Environment.NewLine - + Environment.NewLine - + "[a] --|> [b]" - + Environment.NewLine - + "[b] --|> [c]" - + Environment.NewLine - + "[c] --|> [a]" - + Environment.NewLine - + "@enduml" - + Environment.NewLine; - Assert.Equal(expectedUml, uml); + return VerifyElements(Dependencies); } [Fact] - public void BuildUmlByDependenciesWithObjectsWithNoDependenciesTest() + public Task BuildUmlByDependenciesWithObjectsWithNoDependenciesTest() { var classesWithoutDependencies = new[] { new PlantUmlClass("d") }; - var builder = new PlantUmlFileBuilder().WithElements( - Dependencies.Concat(classesWithoutDependencies) - ); - var uml = builder.AsString(); - Assert.NotEmpty(uml); - - var expectedUml = - "@startuml" - + Environment.NewLine - + Environment.NewLine - + "!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml" - + Environment.NewLine - + Environment.NewLine - + "HIDE_STEREOTYPE()" - + Environment.NewLine - + Environment.NewLine - + "class \"d\" {" - + Environment.NewLine - + "}" - + Environment.NewLine - + "[a] --|> [b]" - + Environment.NewLine - + "[b] --|> [c]" - + Environment.NewLine - + "[c] --|> [a]" - + Environment.NewLine - + "@enduml" - + Environment.NewLine; - Assert.Equal(expectedUml, uml); + return VerifyElements(Dependencies.Concat(classesWithoutDependencies)); } [Fact] @@ -136,6 +88,64 @@ public void SliceWithHyperlinkAppendsHyperlinkTest() Assert.Equal("[Slice1] [[https://example.com]] " + Environment.NewLine, uml); } + [Fact] + public Task NestedSliceWithHyperlinkTest() + { + return VerifyElements( + new[] { new PlantUmlSlice("A.B.C", "A.", hyperlink: "https://example.com") } + ); + } + + [Fact] + public Task SlicesSharingParentPackagesTest() + { + return VerifyElements( + new[] + { + new PlantUmlSlice("A.X.One", "A."), + new PlantUmlSlice("A.Y.Two", "A."), + new PlantUmlSlice("A.X.Deep.Three", "A."), + new PlantUmlSlice("B.Four", "B."), + } + ); + } + + [Fact] + public Task PackageOnlySliceWithColorTest() + { + return VerifyElements( + new[] + { + new PlantUmlSlice("A.X.One", "A."), + new PlantUmlSlice("A.X.", "A.", "99ffd1"), + } + ); + } + + [Fact] + public Task FlatSlicesNextToNestedSlicesTest() + { + return VerifyElements( + new[] + { + new PlantUmlSlice("Flat1"), + new PlantUmlSlice("A.X", "A."), + new PlantUmlSlice("Flat2"), + new PlantUmlSlice("A.Y", "A."), + } + ); + } + + [Fact] + public Task C4StyleSlicesSharingParentBoundariesTest() + { + var one = new PlantUmlSlice("A.X.One", "A."); + var two = new PlantUmlSlice("A.X.Two", "A."); + one.UseS4Style(); + two.UseS4Style(); + return VerifyElements(new[] { one, two }); + } + [Fact] public void HandleIllegalComponentNamesTest() { @@ -160,7 +170,7 @@ public void HandleIllegalComponentNamesTest() } [Fact] - public void SpecialCharactersInComponentNamesTest() + public Task SpecialCharactersInComponentNamesTest() { var dependenciesWithSpecialCharacters = new List { @@ -172,35 +182,17 @@ public void SpecialCharactersInComponentNamesTest() { new PlantUmlClass("!§´`$%&/()=?\\\täöüß^°-*+,-.,;:<>|@€"), }; - var builder = new PlantUmlFileBuilder().WithElements( + return VerifyElements( dependenciesWithSpecialCharacters.Concat(classesWithSpecialCharacters) ); - var uml = builder.AsString(); - Assert.NotEmpty(uml); + } - var expectedUml = - "@startuml" - + Environment.NewLine - + Environment.NewLine - + "!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml" - + Environment.NewLine - + Environment.NewLine - + "HIDE_STEREOTYPE()" - + Environment.NewLine - + Environment.NewLine - + "class \"!§´`$%&/()=?\\\täöüß^°-*+,-.,;:<>|@€\" {" - + Environment.NewLine - + "}" - + Environment.NewLine - + "[!\"§´`] --|> [$%&/()=?]" - + Environment.NewLine - + "[\\\t%] --|> [äöüß]" - + Environment.NewLine - + "[^°-*+.,;:] --|> [<>|@€]" - + Environment.NewLine - + "@enduml" - + Environment.NewLine; - Assert.Equal(expectedUml, uml); + private static Task VerifyElements(IEnumerable elements) + { + return Verifier + .Verify(new PlantUmlFileBuilder().WithElements(elements).AsString()) + .DisableDiff() // Don't open diff tool during the test + .UseDirectory("Snapshots"); } } diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.BuildUmlByDependenciesTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.BuildUmlByDependenciesTest.verified.txt new file mode 100644 index 000000000..ce6518e6a --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.BuildUmlByDependenciesTest.verified.txt @@ -0,0 +1,10 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +[a] --|> [b] +[b] --|> [c] +[c] --|> [a] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.BuildUmlByDependenciesWithObjectsWithNoDependenciesTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.BuildUmlByDependenciesWithObjectsWithNoDependenciesTest.verified.txt new file mode 100644 index 000000000..59d29273b --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.BuildUmlByDependenciesWithObjectsWithNoDependenciesTest.verified.txt @@ -0,0 +1,12 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +class "d" { +} +[a] --|> [b] +[b] --|> [c] +[c] --|> [a] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSlicesSharingParentBoundariesTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSlicesSharingParentBoundariesTest.verified.txt new file mode 100644 index 000000000..fef7d7e38 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSlicesSharingParentBoundariesTest.verified.txt @@ -0,0 +1,19 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +Boundary(A, A) { +Boundary(X, X) { +Container(A.X.One, One) +} +} + +Boundary(A, A) { +Boundary(X, X) { +Container(A.X.Two, Two) +} +} + +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.FlatSlicesNextToNestedSlicesTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.FlatSlicesNextToNestedSlicesTest.verified.txt new file mode 100644 index 000000000..aadbf8bec --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.FlatSlicesNextToNestedSlicesTest.verified.txt @@ -0,0 +1,17 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +[Flat1] +package A { +[X] as A.X +} + +[Flat2] +package A { +[Y] as A.Y +} + +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.NestedSliceWithHyperlinkTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.NestedSliceWithHyperlinkTest.verified.txt new file mode 100644 index 000000000..9b4765d2f --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.NestedSliceWithHyperlinkTest.verified.txt @@ -0,0 +1,13 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package A { +package B { +[C] as A.B.C +} +} + [[https://example.com]] +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.PackageOnlySliceWithColorTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.PackageOnlySliceWithColorTest.verified.txt new file mode 100644 index 000000000..fb4f3b3a0 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.PackageOnlySliceWithColorTest.verified.txt @@ -0,0 +1,18 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package A { +package X { +[One] as A.X.One +} +} + +package A { +package X #99ffd1 { +} +} + +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SlicesSharingParentPackagesTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SlicesSharingParentPackagesTest.verified.txt new file mode 100644 index 000000000..8fa3e866c --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SlicesSharingParentPackagesTest.verified.txt @@ -0,0 +1,31 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package A { +package X { +[One] as A.X.One +} +} + +package A { +package Y { +[Two] as A.Y.Two +} +} + +package A { +package X { +package Deep { +[Three] as A.X.Deep.Three +} +} +} + +package B { +[Four] as B.Four +} + +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SpecialCharactersInComponentNamesTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SpecialCharactersInComponentNamesTest.verified.txt new file mode 100644 index 000000000..f7a8e9730 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SpecialCharactersInComponentNamesTest.verified.txt @@ -0,0 +1,12 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +class "!§´`$%&/()=?\ äöüß^°-*+,-.,;:<>|@€" { +} +[!"§´`] --|> [$%&/()=?] +[\ %] --|> [äöüß] +[^°-*+.,;:] --|> [<>|@€] +@enduml From 4f575b35b31039dd83f4964991583da10923f455 Mon Sep 17 00:00:00 2001 From: Alexander Linne Date: Fri, 25 Sep 2026 11:44:36 +0200 Subject: [PATCH 2/4] refactor: emit slice packages as a single tree PlantUmlSlice rendered every nested slice on its own, together with the full chain of packages around it, so a diagram re-opened each shared parent package once per slice. PlantUML merges re-opened packages, so the drawing was fine, but the source read nothing like a hand-written diagram. PlantUmlDiagram now collects the nested slices into a PlantUmlSliceTree and renders each root package once, where its first slice used to be. Slices without a namespace keep their position. A package-only slice (as used by focus-on) colours its package node instead of re-opening it. The rendered diagrams are unchanged: the old and new snapshots produce the same clusters, entities, links and fills. While moving the component line, put the hyperlink in front of the colour. PlantUML rejects "[X] #color [[link]]" as a syntax error, and for nested slices the link used to be appended after the closing braces. Indent the contents of every package and boundary by two spaces per level, as a hand-written diagram would. Signed-off-by: Alexander Linne --- .../Domain/PlantUml/Export/PlantUmlDiagram.cs | 52 +++++-- .../Domain/PlantUml/Export/PlantUmlSlice.cs | 139 ++++++++---------- .../PlantUml/Export/PlantUmlSliceTree.cs | 116 +++++++++++++++ .../PlantUml/PlantUmlFileBuilderTest.cs | 12 ++ ...esSharingParentBoundariesTest.verified.txt | 15 +- ...tSlicesNextToNestedSlicesTest.verified.txt | 7 +- ....NestedSliceWithHyperlinkTest.verified.txt | 8 +- ...PackageOnlySliceWithColorTest.verified.txt | 11 +- ...icesSharingParentPackagesTest.verified.txt | 28 ++-- ...Tests.BuildUmlBySlicesFocusOn.verified.txt | 27 +--- ...SkipsSlicesWithoutFocusString.verified.txt | 18 +-- ...sMatchingWithPackages_C4Style.verified.txt | 41 ++---- ...hingWithPackages_DirectCircle.verified.txt | 7 +- ...ngWithPackages_DoubleAsterisk.verified.txt | 39 ++--- ...ithPackages_LimitDependencies.verified.txt | 39 ++--- ...ackages_MultipleCaptureGroups.verified.txt | 31 ++-- ...es_NonContiguousCaptureGroups.verified.txt | 31 ++-- ...ngWithPackages_SingleAsterisk.verified.txt | 12 +- 18 files changed, 337 insertions(+), 296 deletions(-) create mode 100644 ArchUnitNET/Domain/PlantUml/Export/PlantUmlSliceTree.cs diff --git a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDiagram.cs b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDiagram.cs index bdc6996a7..6d808cd65 100644 --- a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDiagram.cs +++ b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDiagram.cs @@ -29,20 +29,48 @@ public string GetPlantUmlString(RenderOptions renderOptions) ) .AppendLine(); result.AppendLine("HIDE_STEREOTYPE()").AppendLine(); - result.Append( - PlantUmlElements - .OrderBy(element => element.GetType() != typeof(PlantUmlNamespace)) - .ThenBy(element => element.GetType() != typeof(PlantUmlSlice)) - .ThenBy(element => element.GetType() != typeof(PlantUmlClass)) - .ThenBy(element => element.GetType() != typeof(PlantUmlInterface)) - .Aggregate( - "", - (umlString, umlElement) => - umlString + umlElement.GetPlantUmlString(renderOptions) - ) - ); + var orderedElements = PlantUmlElements + .OrderBy(element => element.GetType() != typeof(PlantUmlNamespace)) + .ThenBy(element => element.GetType() != typeof(PlantUmlSlice)) + .ThenBy(element => element.GetType() != typeof(PlantUmlClass)) + .ThenBy(element => element.GetType() != typeof(PlantUmlInterface)); + foreach (var element in MergeNestedSlices(orderedElements)) + { + result.Append(element.GetPlantUmlString(renderOptions)); + } result.AppendLine("@enduml"); return result.ToString(); } + + /// + /// Replaces all nested slices that share a root package with a single tree for that + /// package, placed where its first slice was. All other elements keep their position. + /// + private static IEnumerable MergeNestedSlices( + IEnumerable elements + ) + { + var mergedElements = new List(); + var sliceTrees = new Dictionary(); + foreach (var element in elements) + { + if (!(element is PlantUmlSlice slice) || !slice.IsNested) + { + mergedElements.Add(element); + } + else if (sliceTrees.TryGetValue(slice.RootPackage, out var tree)) + { + tree.Add(slice); + } + else + { + tree = new PlantUmlSliceTree(slice); + sliceTrees.Add(slice.RootPackage, tree); + mergedElements.Add(tree); + } + } + + return mergedElements; + } } } diff --git a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSlice.cs b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSlice.cs index ad4aa0405..236adf340 100644 --- a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSlice.cs +++ b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSlice.cs @@ -1,6 +1,5 @@ -using System; -using System.ComponentModel.Design; -using System.Diagnostics; +using System.Collections.Generic; +using System.Linq; using System.Text; namespace ArchUnitNET.Domain.PlantUml.Export @@ -38,111 +37,99 @@ public void UseS4Style() C4Style = true; } - public string GetPlantUmlString(RenderOptions renderOptions) - { - var result = C4Style ? BuildStringC4Style() : BuildString(); + internal bool IsNested => Namespace != null; - if (Hyperlink != null) + internal bool IsC4Style => C4Style; + + internal string SliceColor => Color; + + /// + /// The outermost package this slice is nested in, which is its whole namespace. + /// + internal string RootPackage => Namespace.Remove(Namespace.Length - 1); + + /// + /// The packages this slice is nested in, outermost first. The first entry is the whole + /// namespace of the slice, the others are the segments between it and the leaf. + /// + internal IReadOnlyList PackagePath + { + get { - result.Append(" [[" + Hyperlink + "]] "); + var path = new List { RootPackage }; + var segments = _name.Remove(0, Namespace.Length).Split('.'); + path.AddRange(segments.Take(segments.Length - 1)); + return path; } - - return result.AppendLine().ToString(); } - private StringBuilder BuildString() + /// + /// The label of the component inside its innermost package, or an empty string if the + /// slice only stands for that package. + /// + internal string Leaf { - var result = new StringBuilder(); - if (Namespace != null) + get { - result.Append("package " + Namespace.Remove(Namespace.Length - 1)); var name = _name.Remove(0, Namespace.Length); - var iter = 1; - while (name.Contains(".")) - { - var dotPattern = name.IndexOf(".", StringComparison.Ordinal); - result.AppendLine(" {"); - result.Append("package " + name.Remove(dotPattern)); - name = name.Remove(0, dotPattern + 1); - iter++; - } + return name.Substring(name.LastIndexOf('.') + 1); + } + } - if (name != "") - { - result.AppendLine(" {"); - result.Append("[" + name + "] as " + _name); - if (Color != null) - { - result.AppendLine(" #" + Color); - } - else - { - result.AppendLine(); - } - } - else if (Color != null) - { - result.AppendLine(" #" + Color + " {"); - } - else - { - result.AppendLine(" {"); - } + public string GetPlantUmlString(RenderOptions renderOptions) + { + if (IsNested) + { + return new PlantUmlSliceTree(this).GetPlantUmlString(renderOptions); + } - for (var i = iter; i > 0; i--) - { - result.AppendLine("}"); - } + var result = new StringBuilder(); + if (C4Style) + { + result.Append("Container(" + _name + ", " + _name + ")"); + AppendHyperlink(result); } else { result.Append("[" + _name + "]"); + AppendHyperlink(result); if (Color != null) { result.Append(" #" + Color); } } - return result; + return result.AppendLine().ToString(); } - private StringBuilder BuildStringC4Style() + internal string GetLeafString() { var result = new StringBuilder(); - if (Namespace == null) + if (C4Style) { - result.Append("Container(" + _name + ", " + _name + ")"); - return result; + result.Append("Container(" + _name + ", " + Leaf + ")"); + AppendHyperlink(result); } - - var namespc = Namespace.Remove(Namespace.Length - 1); - result.Append("Boundary(" + namespc + ", " + namespc + ") "); - var name = _name.Remove(0, Namespace.Length); - var iter = 1; - while (name.Contains(".")) + else { - var dotPattern = name.IndexOf(".", StringComparison.Ordinal); - result.AppendLine(" {"); - result.Append( - "Boundary(" + name.Remove(dotPattern) + ", " + name.Remove(dotPattern) + ") " - ); - name = name.Remove(0, dotPattern + 1); - iter++; + result.Append("[" + Leaf + "] as " + _name); + AppendHyperlink(result); + if (Color != null) + { + result.Append(" #" + Color); + } } - result.AppendLine(" {"); - if (name != "") - { - result.Append("Container(" + _name + ", " + name + ")"); - result.AppendLine(); - } + return result.ToString(); + } - for (var i = iter; i > 0; i--) + // PlantUML only accepts a link in front of the color, not after it. + private void AppendHyperlink(StringBuilder result) + { + if (Hyperlink != null) { - result.AppendLine("}"); + result.Append(" [[" + Hyperlink + "]] "); } - - return result; } } } diff --git a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSliceTree.cs b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSliceTree.cs new file mode 100644 index 000000000..90bfca4ef --- /dev/null +++ b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSliceTree.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ArchUnitNET.Domain.PlantUml.Export +{ + /// + /// A package (or C4 boundary) that holds nested slices, rendered as a single block. + /// + /// + /// Every nested slice carries the full chain of packages it lives in. Rendering each slice on + /// its own would re-open its parent packages once per slice. PlantUML merges those re-opened + /// packages, but the source then reads nothing like a hand-written diagram. Collecting the + /// slices into a tree first opens every package exactly once. + /// + internal class PlantUmlSliceTree : IPlantUmlElement + { + private readonly string _name; + private readonly bool _c4Style; + private string _color; + + // Child packages and components in the order they were first seen, so the output keeps + // the order in which the slices were added. + private readonly List _children = new List(); + + /// + /// Creates the tree for the root package of the given nested slice, holding that slice. + /// + public PlantUmlSliceTree(PlantUmlSlice slice) + : this(slice.RootPackage, slice.IsC4Style) + { + Add(slice); + } + + private PlantUmlSliceTree(string name, bool c4Style) + { + _name = name; + _c4Style = c4Style; + } + + /// + /// Adds a nested slice whose outermost package is the root of this tree, creating the + /// packages between the root and the slice as needed. + /// + public void Add(PlantUmlSlice slice) + { + var node = this; + foreach (var segment in slice.PackagePath.Skip(1)) + { + node = node.GetOrAddPackage(segment, slice.IsC4Style); + } + + if (slice.Leaf != "") + { + node._children.Add(slice); + } + else if (slice.SliceColor != null) + { + node._color = slice.SliceColor; + } + } + + public string GetPlantUmlString(RenderOptions renderOptions) + { + var result = new StringBuilder(); + Render(result, 0); + return result.AppendLine().ToString(); + } + + private PlantUmlSliceTree GetOrAddPackage(string name, bool c4Style) + { + var package = _children + .OfType() + .FirstOrDefault(child => child._name == name); + if (package == null) + { + package = new PlantUmlSliceTree(name, c4Style); + _children.Add(package); + } + + return package; + } + + private void Render(StringBuilder result, int depth) + { + var indent = new string(' ', 2 * depth); + var childIndent = new string(' ', 2 * (depth + 1)); + if (_c4Style) + { + result.AppendLine(indent + "Boundary(" + _name + ", " + _name + ") {"); + } + else if (_color != null) + { + result.AppendLine(indent + "package " + _name + " #" + _color + " {"); + } + else + { + result.AppendLine(indent + "package " + _name + " {"); + } + + foreach (var child in _children) + { + if (child is PlantUmlSliceTree package) + { + package.Render(result, depth + 1); + } + else + { + result.AppendLine(childIndent + ((PlantUmlSlice)child).GetLeafString()); + } + } + + result.AppendLine(indent + "}"); + } + } +} diff --git a/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs b/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs index a1991ef8e..d226fd5cc 100644 --- a/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs +++ b/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs @@ -88,6 +88,18 @@ public void SliceWithHyperlinkAppendsHyperlinkTest() Assert.Equal("[Slice1] [[https://example.com]] " + Environment.NewLine, uml); } + [Fact] + public void SliceWithHyperlinkAndColorPutsHyperlinkBeforeColorTest() + { + var slice = new PlantUmlSlice( + "Slice1", + color: "99ffd1", + hyperlink: "https://example.com" + ); + var uml = slice.GetPlantUmlString(new RenderOptions()); + Assert.Equal("[Slice1] [[https://example.com]] #99ffd1" + Environment.NewLine, uml); + } + [Fact] public Task NestedSliceWithHyperlinkTest() { diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSlicesSharingParentBoundariesTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSlicesSharingParentBoundariesTest.verified.txt index fef7d7e38..85eda9bd0 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSlicesSharingParentBoundariesTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSlicesSharingParentBoundariesTest.verified.txt @@ -4,16 +4,11 @@ HIDE_STEREOTYPE() -Boundary(A, A) { -Boundary(X, X) { -Container(A.X.One, One) -} -} - -Boundary(A, A) { -Boundary(X, X) { -Container(A.X.Two, Two) -} +Boundary(A, A) { + Boundary(X, X) { + Container(A.X.One, One) + Container(A.X.Two, Two) + } } @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.FlatSlicesNextToNestedSlicesTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.FlatSlicesNextToNestedSlicesTest.verified.txt index aadbf8bec..a1d677810 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.FlatSlicesNextToNestedSlicesTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.FlatSlicesNextToNestedSlicesTest.verified.txt @@ -6,12 +6,9 @@ HIDE_STEREOTYPE() [Flat1] package A { -[X] as A.X + [X] as A.X + [Y] as A.Y } [Flat2] -package A { -[Y] as A.Y -} - @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.NestedSliceWithHyperlinkTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.NestedSliceWithHyperlinkTest.verified.txt index 9b4765d2f..41b08885b 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.NestedSliceWithHyperlinkTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.NestedSliceWithHyperlinkTest.verified.txt @@ -5,9 +5,9 @@ HIDE_STEREOTYPE() package A { -package B { -[C] as A.B.C + package B { + [C] as A.B.C [[https://example.com]] + } } -} - [[https://example.com]] + @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.PackageOnlySliceWithColorTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.PackageOnlySliceWithColorTest.verified.txt index fb4f3b3a0..342ac1f57 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.PackageOnlySliceWithColorTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.PackageOnlySliceWithColorTest.verified.txt @@ -5,14 +5,9 @@ HIDE_STEREOTYPE() package A { -package X { -[One] as A.X.One -} -} - -package A { -package X #99ffd1 { -} + package X #99ffd1 { + [One] as A.X.One + } } @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SlicesSharingParentPackagesTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SlicesSharingParentPackagesTest.verified.txt index 8fa3e866c..49f5a0af5 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SlicesSharingParentPackagesTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SlicesSharingParentPackagesTest.verified.txt @@ -5,27 +5,19 @@ HIDE_STEREOTYPE() package A { -package X { -[One] as A.X.One -} -} - -package A { -package Y { -[Two] as A.Y.Two -} -} - -package A { -package X { -package Deep { -[Three] as A.X.Deep.Three -} -} + package X { + [One] as A.X.One + package Deep { + [Three] as A.X.Deep.Three + } + } + package Y { + [Two] as A.Y.Two + } } package B { -[Four] as B.Four + [Four] as B.Four } @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt index bf61e1915..7e6938693 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt @@ -5,25 +5,14 @@ HIDE_STEREOTYPE() package SlicesTestAssembly.MultipleSubnamespaces { -package Slice1 { -[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service #99ffd1 -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice2 { -[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice3 { -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice1 #99ffd1 { -} + package Slice1 #99ffd1 { + [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service #99ffd1 + } + package Slice2 { + [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service + } + package Slice3 { + } } [SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt index 01d54e434..125f56448 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt @@ -5,18 +5,12 @@ HIDE_STEREOTYPE() package SlicesTestAssembly.MultipleSubnamespaces { -package Slice1 { -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice2 { -[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -[Slice3] as SlicesTestAssembly.MultipleSubnamespaces.Slice3 #99ffd1 + package Slice1 { + } + package Slice2 { + [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service + } + [Slice3] as SlicesTestAssembly.MultipleSubnamespaces.Slice3 #99ffd1 } [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice3] diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt index cec8d4002..c4f1d5c77 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt @@ -4,32 +4,21 @@ HIDE_STEREOTYPE() -Boundary(SlicesTestAssembly.MultipleSubnamespaces, SlicesTestAssembly.MultipleSubnamespaces) { -Boundary(Slice1, Slice1) { -Container(SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service, Service) -} -} - -Boundary(SlicesTestAssembly.MultipleSubnamespaces, SlicesTestAssembly.MultipleSubnamespaces) { -Boundary(Slice2, Slice2) { -Container(SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service, Service) -} -} - -Boundary(SlicesTestAssembly.MultipleSubnamespaces, SlicesTestAssembly.MultipleSubnamespaces) { -Boundary(Slice3, Slice3) { -Boundary(Group1, Group1) { -Container(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner, Inner) -} -} -} - -Boundary(SlicesTestAssembly.MultipleSubnamespaces, SlicesTestAssembly.MultipleSubnamespaces) { -Boundary(Slice3, Slice3) { -Boundary(Group2, Group2) { -Container(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner, Inner) -} -} +Boundary(SlicesTestAssembly.MultipleSubnamespaces, SlicesTestAssembly.MultipleSubnamespaces) { + Boundary(Slice1, Slice1) { + Container(SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service, Service) + } + Boundary(Slice2, Slice2) { + Container(SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service, Service) + } + Boundary(Slice3, Slice3) { + Boundary(Group1, Group1) { + Container(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner, Inner) + } + Boundary(Group2, Group2) { + Container(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner, Inner) + } + } } [SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt index d1981eff7..b9b4c33f2 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt @@ -5,11 +5,8 @@ HIDE_STEREOTYPE() package SlicesTestAssembly.DirectCircle { -[Slice1] as SlicesTestAssembly.DirectCircle.Slice1 -} - -package SlicesTestAssembly.DirectCircle { -[Slice2] as SlicesTestAssembly.DirectCircle.Slice2 + [Slice1] as SlicesTestAssembly.DirectCircle.Slice1 + [Slice2] as SlicesTestAssembly.DirectCircle.Slice2 } [SlicesTestAssembly.DirectCircle.Slice1] <-[#red]> [SlicesTestAssembly.DirectCircle.Slice2] diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt index 2e17772dc..86fa590af 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt @@ -5,31 +5,20 @@ HIDE_STEREOTYPE() package SlicesTestAssembly.MultipleSubnamespaces { -package Slice1 { -[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice2 { -[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice3 { -package Group1 { -[Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner -} -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice3 { -package Group2 { -[Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner -} -} + package Slice1 { + [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service + } + package Slice2 { + [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service + } + package Slice3 { + package Group1 { + [Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner + } + package Group2 { + [Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner + } + } } [SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt index 44b61fc01..eb456f572 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt @@ -5,31 +5,20 @@ HIDE_STEREOTYPE() package SlicesTestAssembly.MultipleSubnamespaces { -package Slice1 { -[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice2 { -[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice3 { -package Group1 { -[Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner -} -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice3 { -package Group2 { -[Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner -} -} + package Slice1 { + [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service + } + package Slice2 { + [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service + } + package Slice3 { + package Group1 { + [Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner + } + package Group2 { + [Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner + } + } } Slice1 ..> Slice2 diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt index 9416d0726..64eb76fc3 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt @@ -5,27 +5,16 @@ HIDE_STEREOTYPE() package SlicesTestAssembly.MultipleSubnamespaces { -package Slice1 { -[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice2 { -[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice3 { -[Group1] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice3 { -[Group2] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 -} + package Slice1 { + [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service + } + package Slice2 { + [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service + } + package Slice3 { + [Group1] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 + [Group2] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 + } } [SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt index 9416d0726..64eb76fc3 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt @@ -5,27 +5,16 @@ HIDE_STEREOTYPE() package SlicesTestAssembly.MultipleSubnamespaces { -package Slice1 { -[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice2 { -[Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice3 { -[Group1] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 -} -} - -package SlicesTestAssembly.MultipleSubnamespaces { -package Slice3 { -[Group2] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 -} + package Slice1 { + [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service + } + package Slice2 { + [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service + } + package Slice3 { + [Group1] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 + [Group2] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 + } } [SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt index 6e0cbf47d..dfe95dc0e 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt @@ -5,15 +5,9 @@ HIDE_STEREOTYPE() package SlicesTestAssembly.MultipleSubnamespaces { -[Slice1] as SlicesTestAssembly.MultipleSubnamespaces.Slice1 -} - -package SlicesTestAssembly.MultipleSubnamespaces { -[Slice2] as SlicesTestAssembly.MultipleSubnamespaces.Slice2 -} - -package SlicesTestAssembly.MultipleSubnamespaces { -[Slice3] as SlicesTestAssembly.MultipleSubnamespaces.Slice3 + [Slice1] as SlicesTestAssembly.MultipleSubnamespaces.Slice1 + [Slice2] as SlicesTestAssembly.MultipleSubnamespaces.Slice2 + [Slice3] as SlicesTestAssembly.MultipleSubnamespaces.Slice3 } [SlicesTestAssembly.MultipleSubnamespaces.Slice1] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2] From cf80f5ba39be205f6c37d0ed1327ab7714b25151 Mon Sep 17 00:00:00 2001 From: Alexander Linne Date: Fri, 25 Sep 2026 12:29:57 +0200 Subject: [PATCH 3/4] test: pin PlantUML output for same-named packages under different parents PlantUML identifies a package by its bare name, not by where it is nested. The slice exporter writes every package by its last segment and points package-level arrows at that segment as well, so two packages called "Domain" under "Orders" and "Billing" are drawn as one: both "Model" components end up in the same "Domain", and the other parent is left as an empty package. An arrow into "Billing.Domain" is written as "-> Domain" and ends in whichever "Domain" PlantUML saw first. Add a SameNamedSubnamespaces fixture in which Orders and Billing both contain Domain.Model and Web depends on Billing.Domain, and pin what the exporter currently produces for it, in plain and C4 style. Pin the builder output for the same shape, the package arrows of every dependency type and an empty package, which the fix has to keep readable. Signed-off-by: Alexander Linne --- .../PlantUml/PlantUmlFileBuilderTest.cs | 72 +++++++++++++++++++ .../PlantUml/PlantUmlSliceDiagramTests.cs | 25 +++++++ ...riesUnderDifferentParentsTest.verified.txt | 20 ++++++ ...eBuilderTest.EmptyPackageTest.verified.txt | 15 ++++ ...agesUnderDifferentParentsTest.verified.txt | 20 ++++++ ...ithPackages_SameNamedPackages.verified.txt | 23 ++++++ ...ges_SameNamedPackages_C4Style.verified.txt | 23 ++++++ .../Billing/Domain/BillingDomainClass.cs | 3 + .../Billing/Domain/Model/BillingModelClass.cs | 3 + .../Orders/Domain/Model/OrdersModelClass.cs | 8 +++ .../Orders/Domain/OrdersDomainClass.cs | 8 +++ .../SameNamedSubnamespaces/Web/WebClass.cs | 8 +++ 12 files changed, 228 insertions(+) create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSameNamedBoundariesUnderDifferentParentsTest.verified.txt create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.EmptyPackageTest.verified.txt create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SameNamedPackagesUnderDifferentParentsTest.verified.txt create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages.verified.txt create mode 100644 ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style.verified.txt create mode 100644 TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Billing/Domain/BillingDomainClass.cs create mode 100644 TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Billing/Domain/Model/BillingModelClass.cs create mode 100644 TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Orders/Domain/Model/OrdersModelClass.cs create mode 100644 TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Orders/Domain/OrdersDomainClass.cs create mode 100644 TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Web/WebClass.cs diff --git a/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs b/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs index d226fd5cc..962479805 100644 --- a/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs +++ b/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs @@ -134,6 +134,18 @@ public Task PackageOnlySliceWithColorTest() ); } + [Fact] + public Task EmptyPackageTest() + { + return VerifyElements( + new[] + { + new PlantUmlSlice("A.X.One", "A."), + new PlantUmlSlice("A.Y.", "A.", "99ffd1"), + } + ); + } + [Fact] public Task FlatSlicesNextToNestedSlicesTest() { @@ -158,6 +170,66 @@ public Task C4StyleSlicesSharingParentBoundariesTest() return VerifyElements(new[] { one, two }); } + [Fact] + public Task SameNamedPackagesUnderDifferentParentsTest() + { + return VerifyElements( + new[] + { + new PlantUmlSlice("A.Orders.Domain.Model", "A."), + new PlantUmlSlice("A.Billing.Domain.Model", "A."), + } + ); + } + + [Fact] + public Task C4StyleSameNamedBoundariesUnderDifferentParentsTest() + { + var orders = new PlantUmlSlice("A.Orders.Domain.Model", "A."); + var billing = new PlantUmlSlice("A.Billing.Domain.Model", "A."); + orders.UseS4Style(); + billing.UseS4Style(); + return VerifyElements(new[] { orders, billing }); + } + + [Theory] + [InlineData(DependencyType.OneToPackage, "[A.Web] -[#red]> Domain")] + [InlineData(DependencyType.PackageToOne, "Web -[#blue]> [A.Billing.Domain]")] + [InlineData(DependencyType.PackageToPackage, "Web -[#green]> Domain")] + public void PackageArrowsTest(DependencyType dependencyType, string expected) + { + var dependency = new PlantUmlDependency("A.Web", "A.Billing.Domain", dependencyType); + Assert.Equal(expected + Environment.NewLine, dependency.GetPlantUmlString()); + } + + [Fact] + public void PackageToPackageIfSameParentNamespaceArrowTest() + { + var dependency = new PlantUmlDependency( + "A.Orders.Domain", + "A.Orders.Model", + DependencyType.PackageToPackageIfSameParentNamespace + ); + Assert.Equal("Domain ..> Model" + Environment.NewLine, dependency.GetPlantUmlString()); + } + + [Fact] + public void OneToOneIfSameParentNamespacePackageArrowsTest() + { + var toPackage = new PlantUmlDependency( + "A.Web", + "A.Billing.Domain", + DependencyType.OneToOneIfSameParentNamespace + ); + var fromPackage = new PlantUmlDependency( + "A.Billing.Domain", + "A.Web", + DependencyType.OneToOneIfSameParentNamespace + ); + Assert.Equal("A.Web --> Billing" + Environment.NewLine, toPackage.GetPlantUmlString()); + Assert.Equal("Billing -> A.Web" + Environment.NewLine, fromPackage.GetPlantUmlString()); + } + [Fact] public void HandleIllegalComponentNamesTest() { diff --git a/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs b/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs index f069b07b5..06b29fe24 100644 --- a/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs +++ b/ArchUnitNETTests/Domain/PlantUml/PlantUmlSliceDiagramTests.cs @@ -182,6 +182,31 @@ public Task BuildUmlBySlicesMatchingWithPackages_DirectCircle() return VerifyUml(uml); } + // --- Packages with the same name under different parents stay apart ---------- + // + // Orders and Billing both contain a Domain package. PlantUML identifies a package by + // its name, so unless each one is identified by its full path, both Domain packages + // are drawn as one, and the arrows into Billing.Domain end in Orders.Domain. + + private const string SameNamedRoot = "SlicesTestAssembly.SameNamedSubnamespaces."; + + [Fact] + public Task BuildUmlBySlicesMatchingWithPackages_SameNamedPackages() + { + return VerifySlices( + SliceRuleDefinition.Slices().MatchingWithPackages(SameNamedRoot + "(**)") + ); + } + + [Fact] + public Task BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style() + { + return VerifySlices( + SliceRuleDefinition.Slices().MatchingWithPackages(SameNamedRoot + "(**)"), + new GenerationOptions { C4Style = true } + ); + } + // --- LimitDependencies without namespaces takes the OneToOneCompact branch ---- [Fact] diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSameNamedBoundariesUnderDifferentParentsTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSameNamedBoundariesUnderDifferentParentsTest.verified.txt new file mode 100644 index 000000000..864181307 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSameNamedBoundariesUnderDifferentParentsTest.verified.txt @@ -0,0 +1,20 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +Boundary(A, A) { + Boundary(Orders, Orders) { + Boundary(Domain, Domain) { + Container(A.Orders.Domain.Model, Model) + } + } + Boundary(Billing, Billing) { + Boundary(Domain, Domain) { + Container(A.Billing.Domain.Model, Model) + } + } +} + +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.EmptyPackageTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.EmptyPackageTest.verified.txt new file mode 100644 index 000000000..0d7c96f95 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.EmptyPackageTest.verified.txt @@ -0,0 +1,15 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package A { + package X { + [One] as A.X.One + } + package Y #99ffd1 { + } +} + +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SameNamedPackagesUnderDifferentParentsTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SameNamedPackagesUnderDifferentParentsTest.verified.txt new file mode 100644 index 000000000..c68fa3198 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SameNamedPackagesUnderDifferentParentsTest.verified.txt @@ -0,0 +1,20 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package A { + package Orders { + package Domain { + [Model] as A.Orders.Domain.Model + } + } + package Billing { + package Domain { + [Model] as A.Billing.Domain.Model + } + } +} + +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages.verified.txt new file mode 100644 index 000000000..cbd35aad5 --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages.verified.txt @@ -0,0 +1,23 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +package SlicesTestAssembly.SameNamedSubnamespaces { + package Billing { + package Domain { + [Model] as SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model + } + } + package Orders { + package Domain { + [Model] as SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model + } + } + [Web] as SlicesTestAssembly.SameNamedSubnamespaces.Web +} + +[SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model] --|> [SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model] +[SlicesTestAssembly.SameNamedSubnamespaces.Web] -[#red]> Domain +@enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style.verified.txt new file mode 100644 index 000000000..2db34d03f --- /dev/null +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style.verified.txt @@ -0,0 +1,23 @@ +@startuml + +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +HIDE_STEREOTYPE() + +Boundary(SlicesTestAssembly.SameNamedSubnamespaces, SlicesTestAssembly.SameNamedSubnamespaces) { + Boundary(Billing, Billing) { + Boundary(Domain, Domain) { + Container(SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model, Model) + } + } + Boundary(Orders, Orders) { + Boundary(Domain, Domain) { + Container(SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model, Model) + } + } + Container(SlicesTestAssembly.SameNamedSubnamespaces.Web, Web) +} + +[SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model] --|> [SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model] +[SlicesTestAssembly.SameNamedSubnamespaces.Web] -[#red]> Domain +@enduml diff --git a/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Billing/Domain/BillingDomainClass.cs b/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Billing/Domain/BillingDomainClass.cs new file mode 100644 index 000000000..3909c30fe --- /dev/null +++ b/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Billing/Domain/BillingDomainClass.cs @@ -0,0 +1,3 @@ +namespace SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain; + +public class BillingDomainClass { } diff --git a/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Billing/Domain/Model/BillingModelClass.cs b/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Billing/Domain/Model/BillingModelClass.cs new file mode 100644 index 000000000..05aa00072 --- /dev/null +++ b/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Billing/Domain/Model/BillingModelClass.cs @@ -0,0 +1,3 @@ +namespace SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model; + +public class BillingModelClass { } diff --git a/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Orders/Domain/Model/OrdersModelClass.cs b/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Orders/Domain/Model/OrdersModelClass.cs new file mode 100644 index 000000000..538d2b0f3 --- /dev/null +++ b/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Orders/Domain/Model/OrdersModelClass.cs @@ -0,0 +1,8 @@ +using SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model; + +namespace SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model; + +public class OrdersModelClass +{ + public BillingModelClass Reference = null!; +} diff --git a/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Orders/Domain/OrdersDomainClass.cs b/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Orders/Domain/OrdersDomainClass.cs new file mode 100644 index 000000000..3d018516f --- /dev/null +++ b/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Orders/Domain/OrdersDomainClass.cs @@ -0,0 +1,8 @@ +using SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain; + +namespace SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain; + +public class OrdersDomainClass +{ + public BillingDomainClass Reference = null!; +} diff --git a/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Web/WebClass.cs b/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Web/WebClass.cs new file mode 100644 index 000000000..af2e3e56a --- /dev/null +++ b/TestAssemblies/SlicesTestAssembly/SameNamedSubnamespaces/Web/WebClass.cs @@ -0,0 +1,8 @@ +using SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain; + +namespace SlicesTestAssembly.SameNamedSubnamespaces.Web; + +public class WebClass +{ + public BillingDomainClass Reference = null!; +} From 48e2421a55480e0a4a63d8159541b91f3967c299 Mon Sep 17 00:00:00 2001 From: Alexander Linne Date: Fri, 25 Sep 2026 12:30:49 +0200 Subject: [PATCH 4/4] fix: identify slice packages by their full path PlantUML identifies a package by its bare name, so the exporter merged every two packages with the same last segment into one, and a package arrow ("-> Domain") ended in whichever of them PlantUML registered first. Label every package with its own segment but identify it by its full path ('package "Domain" as App.Billing.Domain', and the same for C4 boundary IDs), and point package arrows at that path. It is the slice description the dependency already holds, so no lookup is needed. Both have to change together: with aliased packages, an arrow to a bare name no longer matches anything and PlantUML draws a free-floating entity for it. An empty package is drawn by PlantUML as a single node that shows its alias above its label once the two differ. Give it a blank placeholder label, so it stays a regular package that shows only its label. That changes the look of the empty packages in the focus-on diagrams from a flat tab to a package box. C4 boundaries do not show their ID and need no placeholder. Apart from that, the existing diagrams render the same clusters, entities, links and fills as before; only the new SameNamedSubnamespaces diagrams change, and now keep both Domain packages apart. Signed-off-by: Alexander Linne --- .../PlantUml/Export/PlantUmlDependency.cs | 29 ++++--------------- .../PlantUml/Export/PlantUmlSliceTree.cs | 28 ++++++++++++++---- .../PlantUml/PlantUmlFileBuilderTest.cs | 21 ++++++++++---- ...riesUnderDifferentParentsTest.verified.txt | 8 ++--- ...esSharingParentBoundariesTest.verified.txt | 2 +- ...eBuilderTest.EmptyPackageTest.verified.txt | 7 +++-- ...tSlicesNextToNestedSlicesTest.verified.txt | 2 +- ....NestedSliceWithHyperlinkTest.verified.txt | 4 +-- ...PackageOnlySliceWithColorTest.verified.txt | 4 +-- ...agesUnderDifferentParentsTest.verified.txt | 10 +++---- ...icesSharingParentPackagesTest.verified.txt | 10 +++---- ...Tests.BuildUmlBySlicesFocusOn.verified.txt | 11 +++---- ...SkipsSlicesWithoutFocusString.verified.txt | 9 +++--- ...sMatchingWithPackages_C4Style.verified.txt | 14 ++++----- ...hingWithPackages_DirectCircle.verified.txt | 2 +- ...ngWithPackages_DoubleAsterisk.verified.txt | 16 +++++----- ...ithPackages_LimitDependencies.verified.txt | 18 ++++++------ ...ackages_MultipleCaptureGroups.verified.txt | 12 ++++---- ...es_NonContiguousCaptureGroups.verified.txt | 12 ++++---- ...ithPackages_SameNamedPackages.verified.txt | 12 ++++---- ...ges_SameNamedPackages_C4Style.verified.txt | 10 +++---- ...ngWithPackages_SingleAsterisk.verified.txt | 2 +- 22 files changed, 127 insertions(+), 116 deletions(-) diff --git a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDependency.cs b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDependency.cs index 50530a951..8191b4b41 100644 --- a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDependency.cs +++ b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlDependency.cs @@ -51,24 +51,13 @@ public string GetPlantUmlString(RenderOptions renderOptions = null) + Environment.NewLine; case DependencyType.OneToPackage: - return "[" - + Origin - + "] -[#red]> " - + GetChildNamespace(Target) - + Environment.NewLine; + return "[" + Origin + "] -[#red]> " + Target + Environment.NewLine; case DependencyType.PackageToOne: - return GetChildNamespace(Origin) - + " -[#blue]> [" - + Target - + "]" - + Environment.NewLine; + return Origin + " -[#blue]> [" + Target + "]" + Environment.NewLine; case DependencyType.PackageToPackage: - return GetChildNamespace(Origin) - + " -[#green]> " - + GetChildNamespace(Target) - + Environment.NewLine; + return Origin + " -[#green]> " + Target + Environment.NewLine; case DependencyType.OneToOneCompact: if (OriginCountOfDots() == TargetCountOfDots()) @@ -93,10 +82,7 @@ public string GetPlantUmlString(RenderOptions renderOptions = null) && (OriginCountOfDots() == 0 || HaveSameParentNamespace(Origin, Target)) ) { - return GetChildNamespace(Origin) - + " ..> " - + GetChildNamespace(Target) - + Environment.NewLine; + return Origin + " ..> " + Target + Environment.NewLine; } return ""; @@ -119,7 +105,7 @@ public string GetPlantUmlString(RenderOptions renderOptions = null) if (tmp != Origin && HaveSameParentNamespace(tmp, Origin)) { - return Origin + " --> " + GetChildNamespace(tmp) + Environment.NewLine; + return Origin + " --> " + tmp + Environment.NewLine; } } else @@ -132,7 +118,7 @@ public string GetPlantUmlString(RenderOptions renderOptions = null) if (tmp != Target && HaveSameParentNamespace(tmp, Target)) { - return GetChildNamespace(tmp) + " -> " + Target + Environment.NewLine; + return tmp + " -> " + Target + Environment.NewLine; } } return ""; @@ -147,9 +133,6 @@ public string GetPlantUmlString(RenderOptions renderOptions = null) private static string GetParentNamespace(string ns) => ns.Remove(ns.LastIndexOf(".", StringComparison.Ordinal)); - private static string GetChildNamespace(string ns) => - ns.Remove(0, ns.LastIndexOf(".", StringComparison.Ordinal) + 1); - private static bool HaveSameParentNamespace(string origin, string target) => (GetParentNamespace(origin) == GetParentNamespace(target)); diff --git a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSliceTree.cs b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSliceTree.cs index 90bfca4ef..6c9ac6e76 100644 --- a/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSliceTree.cs +++ b/ArchUnitNET/Domain/PlantUml/Export/PlantUmlSliceTree.cs @@ -12,10 +12,16 @@ namespace ArchUnitNET.Domain.PlantUml.Export /// its own would re-open its parent packages once per slice. PlantUML merges those re-opened /// packages, but the source then reads nothing like a hand-written diagram. Collecting the /// slices into a tree first opens every package exactly once. + /// + /// PlantUML identifies a package by its name, not by where it is nested, so two packages + /// called "Domain" under different parents would be drawn as one. Every package is therefore + /// labelled with its own segment but identified by its full path, which is also the name the + /// dependencies use to point at it. /// internal class PlantUmlSliceTree : IPlantUmlElement { private readonly string _name; + private readonly string _path; private readonly bool _c4Style; private string _color; @@ -27,14 +33,15 @@ internal class PlantUmlSliceTree : IPlantUmlElement /// Creates the tree for the root package of the given nested slice, holding that slice. /// public PlantUmlSliceTree(PlantUmlSlice slice) - : this(slice.RootPackage, slice.IsC4Style) + : this(slice.RootPackage, slice.RootPackage, slice.IsC4Style) { Add(slice); } - private PlantUmlSliceTree(string name, bool c4Style) + private PlantUmlSliceTree(string name, string path, bool c4Style) { _name = name; + _path = path; _c4Style = c4Style; } @@ -74,7 +81,7 @@ private PlantUmlSliceTree GetOrAddPackage(string name, bool c4Style) .FirstOrDefault(child => child._name == name); if (package == null) { - package = new PlantUmlSliceTree(name, c4Style); + package = new PlantUmlSliceTree(name, _path + "." + name, c4Style); _children.Add(package); } @@ -87,15 +94,24 @@ private void Render(StringBuilder result, int depth) var childIndent = new string(' ', 2 * (depth + 1)); if (_c4Style) { - result.AppendLine(indent + "Boundary(" + _name + ", " + _name + ") {"); + result.AppendLine(indent + "Boundary(" + _path + ", " + _name + ") {"); } else if (_color != null) { - result.AppendLine(indent + "package " + _name + " #" + _color + " {"); + result.AppendLine( + indent + "package \"" + _name + "\" as " + _path + " #" + _color + " {" + ); } else { - result.AppendLine(indent + "package " + _name + " {"); + result.AppendLine(indent + "package \"" + _name + "\" as " + _path + " {"); + } + + // PlantUML draws an empty package as a single node that shows both its label and its + // alias. A blank placeholder keeps it a regular package that only shows its label. + if (!_c4Style && _children.Count == 0) + { + result.AppendLine(childIndent + "label \" \" as " + _path + ".__empty__"); } foreach (var child in _children) diff --git a/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs b/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs index 962479805..e323e0945 100644 --- a/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs +++ b/ArchUnitNETTests/Domain/PlantUml/PlantUmlFileBuilderTest.cs @@ -193,9 +193,9 @@ public Task C4StyleSameNamedBoundariesUnderDifferentParentsTest() } [Theory] - [InlineData(DependencyType.OneToPackage, "[A.Web] -[#red]> Domain")] - [InlineData(DependencyType.PackageToOne, "Web -[#blue]> [A.Billing.Domain]")] - [InlineData(DependencyType.PackageToPackage, "Web -[#green]> Domain")] + [InlineData(DependencyType.OneToPackage, "[A.Web] -[#red]> A.Billing.Domain")] + [InlineData(DependencyType.PackageToOne, "A.Web -[#blue]> [A.Billing.Domain]")] + [InlineData(DependencyType.PackageToPackage, "A.Web -[#green]> A.Billing.Domain")] public void PackageArrowsTest(DependencyType dependencyType, string expected) { var dependency = new PlantUmlDependency("A.Web", "A.Billing.Domain", dependencyType); @@ -210,7 +210,10 @@ public void PackageToPackageIfSameParentNamespaceArrowTest() "A.Orders.Model", DependencyType.PackageToPackageIfSameParentNamespace ); - Assert.Equal("Domain ..> Model" + Environment.NewLine, dependency.GetPlantUmlString()); + Assert.Equal( + "A.Orders.Domain ..> A.Orders.Model" + Environment.NewLine, + dependency.GetPlantUmlString() + ); } [Fact] @@ -226,8 +229,14 @@ public void OneToOneIfSameParentNamespacePackageArrowsTest() "A.Web", DependencyType.OneToOneIfSameParentNamespace ); - Assert.Equal("A.Web --> Billing" + Environment.NewLine, toPackage.GetPlantUmlString()); - Assert.Equal("Billing -> A.Web" + Environment.NewLine, fromPackage.GetPlantUmlString()); + Assert.Equal( + "A.Web --> A.Billing" + Environment.NewLine, + toPackage.GetPlantUmlString() + ); + Assert.Equal( + "A.Billing -> A.Web" + Environment.NewLine, + fromPackage.GetPlantUmlString() + ); } [Fact] diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSameNamedBoundariesUnderDifferentParentsTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSameNamedBoundariesUnderDifferentParentsTest.verified.txt index 864181307..8d130143a 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSameNamedBoundariesUnderDifferentParentsTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSameNamedBoundariesUnderDifferentParentsTest.verified.txt @@ -5,13 +5,13 @@ HIDE_STEREOTYPE() Boundary(A, A) { - Boundary(Orders, Orders) { - Boundary(Domain, Domain) { + Boundary(A.Orders, Orders) { + Boundary(A.Orders.Domain, Domain) { Container(A.Orders.Domain.Model, Model) } } - Boundary(Billing, Billing) { - Boundary(Domain, Domain) { + Boundary(A.Billing, Billing) { + Boundary(A.Billing.Domain, Domain) { Container(A.Billing.Domain.Model, Model) } } diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSlicesSharingParentBoundariesTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSlicesSharingParentBoundariesTest.verified.txt index 85eda9bd0..59dfbabe5 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSlicesSharingParentBoundariesTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.C4StyleSlicesSharingParentBoundariesTest.verified.txt @@ -5,7 +5,7 @@ HIDE_STEREOTYPE() Boundary(A, A) { - Boundary(X, X) { + Boundary(A.X, X) { Container(A.X.One, One) Container(A.X.Two, Two) } diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.EmptyPackageTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.EmptyPackageTest.verified.txt index 0d7c96f95..8fac76fc3 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.EmptyPackageTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.EmptyPackageTest.verified.txt @@ -4,11 +4,12 @@ HIDE_STEREOTYPE() -package A { - package X { +package "A" as A { + package "X" as A.X { [One] as A.X.One } - package Y #99ffd1 { + package "Y" as A.Y #99ffd1 { + label " " as A.Y.__empty__ } } diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.FlatSlicesNextToNestedSlicesTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.FlatSlicesNextToNestedSlicesTest.verified.txt index a1d677810..7eb231dab 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.FlatSlicesNextToNestedSlicesTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.FlatSlicesNextToNestedSlicesTest.verified.txt @@ -5,7 +5,7 @@ HIDE_STEREOTYPE() [Flat1] -package A { +package "A" as A { [X] as A.X [Y] as A.Y } diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.NestedSliceWithHyperlinkTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.NestedSliceWithHyperlinkTest.verified.txt index 41b08885b..43e4fe012 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.NestedSliceWithHyperlinkTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.NestedSliceWithHyperlinkTest.verified.txt @@ -4,8 +4,8 @@ HIDE_STEREOTYPE() -package A { - package B { +package "A" as A { + package "B" as A.B { [C] as A.B.C [[https://example.com]] } } diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.PackageOnlySliceWithColorTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.PackageOnlySliceWithColorTest.verified.txt index 342ac1f57..5adb6e8b4 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.PackageOnlySliceWithColorTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.PackageOnlySliceWithColorTest.verified.txt @@ -4,8 +4,8 @@ HIDE_STEREOTYPE() -package A { - package X #99ffd1 { +package "A" as A { + package "X" as A.X #99ffd1 { [One] as A.X.One } } diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SameNamedPackagesUnderDifferentParentsTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SameNamedPackagesUnderDifferentParentsTest.verified.txt index c68fa3198..3657083ed 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SameNamedPackagesUnderDifferentParentsTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SameNamedPackagesUnderDifferentParentsTest.verified.txt @@ -4,14 +4,14 @@ HIDE_STEREOTYPE() -package A { - package Orders { - package Domain { +package "A" as A { + package "Orders" as A.Orders { + package "Domain" as A.Orders.Domain { [Model] as A.Orders.Domain.Model } } - package Billing { - package Domain { + package "Billing" as A.Billing { + package "Domain" as A.Billing.Domain { [Model] as A.Billing.Domain.Model } } diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SlicesSharingParentPackagesTest.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SlicesSharingParentPackagesTest.verified.txt index 49f5a0af5..509c8eefa 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SlicesSharingParentPackagesTest.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlFileBuilderTest.SlicesSharingParentPackagesTest.verified.txt @@ -4,19 +4,19 @@ HIDE_STEREOTYPE() -package A { - package X { +package "A" as A { + package "X" as A.X { [One] as A.X.One - package Deep { + package "Deep" as A.X.Deep { [Three] as A.X.Deep.Three } } - package Y { + package "Y" as A.Y { [Two] as A.Y.Two } } -package B { +package "B" as B { [Four] as B.Four } diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt index 7e6938693..28668d82b 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn.verified.txt @@ -4,17 +4,18 @@ HIDE_STEREOTYPE() -package SlicesTestAssembly.MultipleSubnamespaces { - package Slice1 #99ffd1 { +package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { + package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 #99ffd1 { [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service #99ffd1 } - package Slice2 { + package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service } - package Slice3 { + package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { + label " " as SlicesTestAssembly.MultipleSubnamespaces.Slice3.__empty__ } } [SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -Slice3 -[#green]> Slice1 +SlicesTestAssembly.MultipleSubnamespaces.Slice3 -[#green]> SlicesTestAssembly.MultipleSubnamespaces.Slice1 @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt index 125f56448..37d98cbf3 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesFocusOn_SingleAsteriskPattern_SkipsSlicesWithoutFocusString.verified.txt @@ -4,15 +4,16 @@ HIDE_STEREOTYPE() -package SlicesTestAssembly.MultipleSubnamespaces { - package Slice1 { +package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { + package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { + label " " as SlicesTestAssembly.MultipleSubnamespaces.Slice1.__empty__ } - package Slice2 { + package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service } [Slice3] as SlicesTestAssembly.MultipleSubnamespaces.Slice3 #99ffd1 } [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice3] -[SlicesTestAssembly.MultipleSubnamespaces.Slice3] -[#red]> Slice1 +[SlicesTestAssembly.MultipleSubnamespaces.Slice3] -[#red]> SlicesTestAssembly.MultipleSubnamespaces.Slice1 @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt index c4f1d5c77..b042a3dba 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_C4Style.verified.txt @@ -5,23 +5,23 @@ HIDE_STEREOTYPE() Boundary(SlicesTestAssembly.MultipleSubnamespaces, SlicesTestAssembly.MultipleSubnamespaces) { - Boundary(Slice1, Slice1) { + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice1, Slice1) { Container(SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service, Service) } - Boundary(Slice2, Slice2) { + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice2, Slice2) { Container(SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service, Service) } - Boundary(Slice3, Slice3) { - Boundary(Group1, Group1) { + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice3, Slice3) { + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1, Group1) { Container(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner, Inner) } - Boundary(Group2, Group2) { + Boundary(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2, Group2) { Container(SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner, Inner) } } } [SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> Slice3 -Slice3 -[#green]> Slice1 +[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> SlicesTestAssembly.MultipleSubnamespaces.Slice3 +SlicesTestAssembly.MultipleSubnamespaces.Slice3 -[#green]> SlicesTestAssembly.MultipleSubnamespaces.Slice1 @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt index b9b4c33f2..1202286f3 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DirectCircle.verified.txt @@ -4,7 +4,7 @@ HIDE_STEREOTYPE() -package SlicesTestAssembly.DirectCircle { +package "SlicesTestAssembly.DirectCircle" as SlicesTestAssembly.DirectCircle { [Slice1] as SlicesTestAssembly.DirectCircle.Slice1 [Slice2] as SlicesTestAssembly.DirectCircle.Slice2 } diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt index 86fa590af..882f21891 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_DoubleAsterisk.verified.txt @@ -4,24 +4,24 @@ HIDE_STEREOTYPE() -package SlicesTestAssembly.MultipleSubnamespaces { - package Slice1 { +package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { + package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service } - package Slice2 { + package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service } - package Slice3 { - package Group1 { + package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { + package "Group1" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 { [Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner } - package Group2 { + package "Group2" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 { [Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner } } } [SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> Slice3 -Slice3 -[#green]> Slice1 +[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> SlicesTestAssembly.MultipleSubnamespaces.Slice3 +SlicesTestAssembly.MultipleSubnamespaces.Slice3 -[#green]> SlicesTestAssembly.MultipleSubnamespaces.Slice1 @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt index eb456f572..a050a9c2d 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_LimitDependencies.verified.txt @@ -4,24 +4,24 @@ HIDE_STEREOTYPE() -package SlicesTestAssembly.MultipleSubnamespaces { - package Slice1 { +package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { + package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service } - package Slice2 { + package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service } - package Slice3 { - package Group1 { + package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { + package "Group1" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 { [Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1.Inner } - package Group2 { + package "Group2" as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 { [Inner] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2.Inner } } } -Slice1 ..> Slice2 -Slice2 ..> Slice3 -Slice3 ..> Slice1 +SlicesTestAssembly.MultipleSubnamespaces.Slice1 ..> SlicesTestAssembly.MultipleSubnamespaces.Slice2 +SlicesTestAssembly.MultipleSubnamespaces.Slice2 ..> SlicesTestAssembly.MultipleSubnamespaces.Slice3 +SlicesTestAssembly.MultipleSubnamespaces.Slice3 ..> SlicesTestAssembly.MultipleSubnamespaces.Slice1 @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt index 64eb76fc3..58bf4a94f 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_MultipleCaptureGroups.verified.txt @@ -4,20 +4,20 @@ HIDE_STEREOTYPE() -package SlicesTestAssembly.MultipleSubnamespaces { - package Slice1 { +package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { + package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service } - package Slice2 { + package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service } - package Slice3 { + package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { [Group1] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 [Group2] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 } } [SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> Slice3 -Slice3 -[#green]> Slice1 +[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> SlicesTestAssembly.MultipleSubnamespaces.Slice3 +SlicesTestAssembly.MultipleSubnamespaces.Slice3 -[#green]> SlicesTestAssembly.MultipleSubnamespaces.Slice1 @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt index 64eb76fc3..58bf4a94f 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_NonContiguousCaptureGroups.verified.txt @@ -4,20 +4,20 @@ HIDE_STEREOTYPE() -package SlicesTestAssembly.MultipleSubnamespaces { - package Slice1 { +package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { + package "Slice1" as SlicesTestAssembly.MultipleSubnamespaces.Slice1 { [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service } - package Slice2 { + package "Slice2" as SlicesTestAssembly.MultipleSubnamespaces.Slice2 { [Service] as SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service } - package Slice3 { + package "Slice3" as SlicesTestAssembly.MultipleSubnamespaces.Slice3 { [Group1] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group1 [Group2] as SlicesTestAssembly.MultipleSubnamespaces.Slice3.Group2 } } [SlicesTestAssembly.MultipleSubnamespaces.Slice1.Service] --|> [SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> Slice3 -Slice3 -[#green]> Slice1 +[SlicesTestAssembly.MultipleSubnamespaces.Slice2.Service] -[#red]> SlicesTestAssembly.MultipleSubnamespaces.Slice3 +SlicesTestAssembly.MultipleSubnamespaces.Slice3 -[#green]> SlicesTestAssembly.MultipleSubnamespaces.Slice1 @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages.verified.txt index cbd35aad5..5338f9f99 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages.verified.txt @@ -4,14 +4,14 @@ HIDE_STEREOTYPE() -package SlicesTestAssembly.SameNamedSubnamespaces { - package Billing { - package Domain { +package "SlicesTestAssembly.SameNamedSubnamespaces" as SlicesTestAssembly.SameNamedSubnamespaces { + package "Billing" as SlicesTestAssembly.SameNamedSubnamespaces.Billing { + package "Domain" as SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain { [Model] as SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model } } - package Orders { - package Domain { + package "Orders" as SlicesTestAssembly.SameNamedSubnamespaces.Orders { + package "Domain" as SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain { [Model] as SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model } } @@ -19,5 +19,5 @@ package SlicesTestAssembly.SameNamedSubnamespaces { } [SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model] --|> [SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model] -[SlicesTestAssembly.SameNamedSubnamespaces.Web] -[#red]> Domain +[SlicesTestAssembly.SameNamedSubnamespaces.Web] -[#red]> SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style.verified.txt index 2db34d03f..4d3dc2e23 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SameNamedPackages_C4Style.verified.txt @@ -5,13 +5,13 @@ HIDE_STEREOTYPE() Boundary(SlicesTestAssembly.SameNamedSubnamespaces, SlicesTestAssembly.SameNamedSubnamespaces) { - Boundary(Billing, Billing) { - Boundary(Domain, Domain) { + Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Billing, Billing) { + Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain, Domain) { Container(SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model, Model) } } - Boundary(Orders, Orders) { - Boundary(Domain, Domain) { + Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Orders, Orders) { + Boundary(SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain, Domain) { Container(SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model, Model) } } @@ -19,5 +19,5 @@ Boundary(SlicesTestAssembly.SameNamedSubnamespaces, SlicesTestAssembly.SameNamed } [SlicesTestAssembly.SameNamedSubnamespaces.Orders.Domain.Model] --|> [SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain.Model] -[SlicesTestAssembly.SameNamedSubnamespaces.Web] -[#red]> Domain +[SlicesTestAssembly.SameNamedSubnamespaces.Web] -[#red]> SlicesTestAssembly.SameNamedSubnamespaces.Billing.Domain @enduml diff --git a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt index dfe95dc0e..63fde3d69 100644 --- a/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt +++ b/ArchUnitNETTests/Domain/PlantUml/Snapshots/PlantUmlSliceDiagramTests.BuildUmlBySlicesMatchingWithPackages_SingleAsterisk.verified.txt @@ -4,7 +4,7 @@ HIDE_STEREOTYPE() -package SlicesTestAssembly.MultipleSubnamespaces { +package "SlicesTestAssembly.MultipleSubnamespaces" as SlicesTestAssembly.MultipleSubnamespaces { [Slice1] as SlicesTestAssembly.MultipleSubnamespaces.Slice1 [Slice2] as SlicesTestAssembly.MultipleSubnamespaces.Slice2 [Slice3] as SlicesTestAssembly.MultipleSubnamespaces.Slice3