From 5b33be76ce6ed180ee38fe1da3527502653d3353 Mon Sep 17 00:00:00 2001 From: Jan Ulrichts Date: Thu, 6 Aug 2026 14:31:56 +0200 Subject: [PATCH 1/3] Added link style tests with expected order of style indices. --- .../FlowchartTests.cs | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/tests/Mermaid.Flowcharts.Tests/FlowchartTests.cs b/tests/Mermaid.Flowcharts.Tests/FlowchartTests.cs index 025c292..63b3e19 100644 --- a/tests/Mermaid.Flowcharts.Tests/FlowchartTests.cs +++ b/tests/Mermaid.Flowcharts.Tests/FlowchartTests.cs @@ -705,6 +705,153 @@ flowchart TD Assert.Equal(expected, actual); } + [Fact] + public void LinkStyleIndices_SubgraphLinkPrecedesFlowchartLink() + { + Flowchart flowchart = new(); + Node a = Node.Create("a", "A"); + Node b = Node.Create("b", "B"); + Node c = Node.Create("c", "C"); + Node d = Node.Create("d", "D"); + + Subgraph sg = Subgraph.Create("sg", "SG"); + sg.AddNode(c).AddNode(d); + sg.AddLink(Link.Create(c, d, linkStyle: new StyleClass(Stroke: new Stroke(Color.FromHex("#00ff00"))))); + + flowchart + .AddNode(a) + .AddNode(b) + .AddNode(sg) + .AddLink(Link.Create(a, b, linkStyle: new StyleClass(Stroke: new Stroke(Color.FromHex("#ff0000"))))); + + string expected = + """ + flowchart TD + a["A"] + b["B"] + + subgraph sg ["SG"] + c["C"] + d["D"] + + c ---> d + end + + a ---> b + + linkStyle 0 stroke:#00ff00 + linkStyle 1 stroke:#ff0000 + + """; + + Assert.Equal(expected, flowchart.ToMermaidString(0, " ")); + } + + [Fact] + public void LinkStyleIndices_SharedStyleAcrossSubgraphs_AndMultipleFlowchartLinks() + { + Flowchart flowchart = new(); + Node a = Node.Create("a", "A"); + Node b = Node.Create("b", "B"); + Node c = Node.Create("c", "C"); + Node d = Node.Create("d", "D"); + Node e = Node.Create("e", "E"); + Node g = Node.Create("g", "G"); + + StyleClass green = new(Stroke: new Stroke(Color.FromHex("#00ff00"))); + + Subgraph sg1 = Subgraph.Create("sg1", "SG1"); + sg1.AddNode(c).AddNode(d); + sg1.AddLink(Link.Create(c, d, linkStyle: green)); + + Subgraph sg2 = Subgraph.Create("sg2", "SG2"); + sg2.AddNode(e).AddNode(g); + sg2.AddLink(Link.Create(e, g, linkStyle: green)); + + flowchart + .AddNode(a) + .AddNode(b) + .AddNode(sg1) + .AddNode(sg2) + .AddLink(Link.Create(a, b, linkStyle: new StyleClass(Stroke: new Stroke(Color.FromHex("#ff0000"))))) + .AddLink(Link.Create(b, a, linkStyle: new StyleClass(Stroke: new Stroke(Color.FromHex("#0000ff"))))); + + string expected = + """ + flowchart TD + a["A"] + b["B"] + + subgraph sg1 ["SG1"] + c["C"] + d["D"] + + c ---> d + end + subgraph sg2 ["SG2"] + e["E"] + g["G"] + + e ---> g + end + + a ---> b + b ---> a + + linkStyle 0,1 stroke:#00ff00 + linkStyle 2 stroke:#ff0000 + linkStyle 3 stroke:#0000ff + + """; + + Assert.Equal(expected, flowchart.ToMermaidString(0, " ")); + } + + [Fact] + public void LinkStyleIndices_NestedSubgraph_InnerLinksRenderBeforeOuterOwnLinks() + { + Flowchart flowchart = new(); + Node a = Node.Create("a", "A"); + Node b = Node.Create("b", "B"); + Node c = Node.Create("c", "C"); + Node d = Node.Create("d", "D"); + + Subgraph inner = Subgraph.Create("inner", "Inner"); + inner.AddNode(c).AddNode(d); + inner.AddLink(Link.Create(c, d, linkStyle: new StyleClass(Stroke: new Stroke(Color.FromHex("#00ff00"))))); + + Subgraph outer = Subgraph.Create("outer", "Outer"); + outer.AddNode(a).AddNode(b).AddNode(inner); + outer.AddLink(Link.Create(a, b, linkStyle: new StyleClass(Stroke: new Stroke(Color.FromHex("#ff0000"))))); + + flowchart.AddNode(outer); + + string expected = + """ + flowchart TD + + subgraph outer ["Outer"] + a["A"] + b["B"] + + subgraph inner ["Inner"] + c["C"] + d["D"] + + c ---> d + end + + a ---> b + end + + linkStyle 0 stroke:#00ff00 + linkStyle 1 stroke:#ff0000 + + """; + + Assert.Equal(expected, flowchart.ToMermaidString(0, " ")); + } + [Fact] public void Flowchart_WhenNestedLinksParentToChild_ShouldNotAddDuplicateNodes() { From a2ab6011cb5f00fa62ef5c0fe0b2ed5354a528bf Mon Sep 17 00:00:00 2001 From: Jan Ulrichts Date: Thu, 6 Aug 2026 15:49:18 +0200 Subject: [PATCH 2/3] Created internal method to build the Mermaid string that assigns link style indices in order of actual link occurrence. --- src/Mermaid.Flowcharts/Flowchart.cs | 22 ++++++++++++++------ src/Mermaid.Flowcharts/Subgraphs/Subgraph.cs | 18 +++++++++++----- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Mermaid.Flowcharts/Flowchart.cs b/src/Mermaid.Flowcharts/Flowchart.cs index 7f654eb..0411895 100644 --- a/src/Mermaid.Flowcharts/Flowchart.cs +++ b/src/Mermaid.Flowcharts/Flowchart.cs @@ -1,24 +1,25 @@ using System.Text; +using System.Runtime.CompilerServices; using Mermaid.Flowcharts.Links; using Mermaid.Flowcharts.Nodes; using Mermaid.Flowcharts.Styling; using Mermaid.Flowcharts.Subgraphs; +[assembly:InternalsVisibleTo("Mermaid.Flowcharts.Tests")] namespace Mermaid.Flowcharts; public class Flowchart : IMermaidPrintable { private readonly List _nodes = []; private readonly List _links = []; + internal IEnumerable AllNodeChildren => _nodes.Concat(Subgraphs.SelectMany(subgraph => subgraph.AllNodeChildren)); + internal IEnumerable AllNodes => Nodes.Concat(Subgraphs.SelectMany(subgraph => subgraph.AllNodes)); public FlowchartTitle? Title { get; } public FlowchartDirection? Direction { get; } public IEnumerable Nodes => _nodes.OfType(); public IEnumerable Subgraphs => _nodes.OfType(); public IEnumerable Links => _links.AsReadOnly(); - public IEnumerable AllNodeChildren => _nodes.Concat(Subgraphs.SelectMany(subgraph => subgraph.AllNodeChildren)); - public IEnumerable AllNodes => Nodes.Concat(Subgraphs.SelectMany(subgraph => subgraph.AllNodes)); - public IEnumerable AllLinks => Links.Concat(Subgraphs.SelectMany(subgraph => subgraph.AllLinks)); public Flowchart(FlowchartDirection? direction = null) { @@ -59,7 +60,7 @@ public Flowchart AddLink(Link link) public override string ToString() => ToMermaidString(); - public string ToMermaidString(int indentations = 0, string indentationText = " ") + internal string ToMermaidStringWithOrderedLinks(int indentations, string indentationText, List linksInOrderOfOccurrence) { StringBuilder flowchartStringBuilder = new(); if (Title is FlowchartTitle title) @@ -68,10 +69,13 @@ public string ToMermaidString(int indentations = 0, string indentationText = " } flowchartStringBuilder.AppendLine($"flowchart {Direction ?? FlowchartDirection.TD}"); + // Add node declarations foreach (Node node in Nodes) { flowchartStringBuilder.AppendLine(node.ToMermaidString(indentations + 1, indentationText)); } + + // Add subgraph declarations if (Subgraphs.Any()) { flowchartStringBuilder.AppendLine(); @@ -79,8 +83,10 @@ public string ToMermaidString(int indentations = 0, string indentationText = " foreach (Subgraph subgraph in Subgraphs) { - flowchartStringBuilder.AppendLine(subgraph.ToMermaidString(indentations + 1, indentationText)); + flowchartStringBuilder.AppendLine(subgraph.ToMermaidStringWithOrderedLinks(indentations + 1, indentationText, linksInOrderOfOccurrence)); } + + // Add link declarations if (_links.Any()) { flowchartStringBuilder.AppendLine(); @@ -88,6 +94,7 @@ public string ToMermaidString(int indentations = 0, string indentationText = " foreach (Link link in _links) { + linksInOrderOfOccurrence.Add(link); flowchartStringBuilder.AppendLine(link.ToMermaidString(indentations + 1, indentationText)); } @@ -124,7 +131,7 @@ public string ToMermaidString(int indentations = 0, string indentationText = " // Group all link styles across flowchart and all subgraphs recursively together Dictionary> distinctLinkStyles = []; - foreach ((Link link, int index) in AllLinks.Select((l, i) => (l, i))) + foreach ((Link link, int index) in linksInOrderOfOccurrence.Select((l, i) => (l, i))) { if (link.LinkStyle is null) { @@ -154,4 +161,7 @@ public string ToMermaidString(int indentations = 0, string indentationText = " return flowchartStringBuilder.ToString(); } + + public string ToMermaidString(int indentations = 0, string indentationText = " ") + => ToMermaidStringWithOrderedLinks(indentations, indentationText, []); } diff --git a/src/Mermaid.Flowcharts/Subgraphs/Subgraph.cs b/src/Mermaid.Flowcharts/Subgraphs/Subgraph.cs index 114a45c..9ffe4e9 100644 --- a/src/Mermaid.Flowcharts/Subgraphs/Subgraph.cs +++ b/src/Mermaid.Flowcharts/Subgraphs/Subgraph.cs @@ -9,6 +9,8 @@ public record Subgraph : INode { private readonly List _nodes = []; private readonly List _links = []; + internal IEnumerable AllNodeChildren => _nodes.Concat(Subgraphs.SelectMany(subgraph => subgraph.AllNodeChildren)); + internal IEnumerable AllNodes => Nodes.Concat(Subgraphs.SelectMany(subgraph => subgraph.AllNodes)); public NodeIdentifier Id { get; } public INodeText Title { get; } @@ -16,9 +18,6 @@ public record Subgraph : INode public IEnumerable Nodes => _nodes.OfType(); public IEnumerable Subgraphs => _nodes.OfType(); public IEnumerable Links => _links.AsReadOnly(); - public IEnumerable AllNodeChildren => _nodes.Concat(Subgraphs.SelectMany(subgraph => subgraph.AllNodeChildren)); - public IEnumerable AllNodes => Nodes.Concat(Subgraphs.SelectMany(subgraph => subgraph.AllNodes)); - public IEnumerable AllLinks => Links.Concat(Subgraphs.SelectMany(subgraph => subgraph.AllLinks)); private Subgraph(NodeIdentifier id, INodeText title, SubgraphDirection? direction = null) { @@ -78,7 +77,7 @@ public Subgraph AddLink(Link link) public override string ToString() => ToMermaidString(); - public string ToMermaidString(int indentations = 0, string indentationText = " ") + internal string ToMermaidStringWithOrderedLinks(int indentations, string indentationText, List linksInOrderOfOccurrence) { StringBuilder subgraphStringBuilder = new(); string indent = indentationText.Repeat(indentations); @@ -88,10 +87,13 @@ public string ToMermaidString(int indentations = 0, string indentationText = " subgraphStringBuilder.AppendLine($"{indent}{indentationText}direction {Direction.Value}"); } + // Add node declarations foreach (Node node in Nodes) { subgraphStringBuilder.AppendLine(node.ToMermaidString(indentations + 1, indentationText)); } + + // Add subgraph declarations if (Subgraphs.Any()) { subgraphStringBuilder.AppendLine(); @@ -99,8 +101,10 @@ public string ToMermaidString(int indentations = 0, string indentationText = " foreach (Subgraph subgraph in Subgraphs) { - subgraphStringBuilder.AppendLine(subgraph.ToMermaidString(indentations + 1, indentationText)); + subgraphStringBuilder.AppendLine(subgraph.ToMermaidStringWithOrderedLinks(indentations + 1, indentationText, linksInOrderOfOccurrence)); } + + // Add link declarations if (_links.Any()) { subgraphStringBuilder.AppendLine(); @@ -108,9 +112,13 @@ public string ToMermaidString(int indentations = 0, string indentationText = " foreach (Link link in _links) { + linksInOrderOfOccurrence.Add(link); subgraphStringBuilder.AppendLine(link.ToMermaidString(indentations + 1, indentationText)); } subgraphStringBuilder.Append($"{indent}end"); return subgraphStringBuilder.ToString(); } + + public string ToMermaidString(int indentations = 0, string indentationText = " ") + => ToMermaidStringWithOrderedLinks(indentations, indentationText, []); } From 55c65f910245aee145a42b13a31f0f80172c257a Mon Sep 17 00:00:00 2001 From: Jan Ulrichts Date: Thu, 6 Aug 2026 15:57:27 +0200 Subject: [PATCH 3/3] Ran formatter. --- src/Mermaid.Flowcharts/Flowchart.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mermaid.Flowcharts/Flowchart.cs b/src/Mermaid.Flowcharts/Flowchart.cs index 0411895..be2ae74 100644 --- a/src/Mermaid.Flowcharts/Flowchart.cs +++ b/src/Mermaid.Flowcharts/Flowchart.cs @@ -5,7 +5,7 @@ using Mermaid.Flowcharts.Styling; using Mermaid.Flowcharts.Subgraphs; -[assembly:InternalsVisibleTo("Mermaid.Flowcharts.Tests")] +[assembly: InternalsVisibleTo("Mermaid.Flowcharts.Tests")] namespace Mermaid.Flowcharts; public class Flowchart : IMermaidPrintable