Skip to content

Commit 339cdbf

Browse files
committed
Add class diagram model primitives
1 parent 414ed81 commit 339cdbf

4 files changed

Lines changed: 116 additions & 0 deletions

File tree

src/DiagramForge/Models/Edge.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ public Edge(string sourceId, string targetId)
2020
/// <summary>Optional label displayed on the edge.</summary>
2121
public Label? Label { get; set; }
2222

23+
/// <summary>Optional label displayed near the source end of the edge.</summary>
24+
public Label? SourceLabel { get; set; }
25+
26+
/// <summary>Optional label displayed near the target end of the edge.</summary>
27+
public Label? TargetLabel { get; set; }
28+
2329
/// <summary>Style of the edge line.</summary>
2430
public EdgeLineStyle LineStyle { get; set; } = EdgeLineStyle.Solid;
2531

src/DiagramForge/Models/Node.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ public Node(string id, string labelText)
3535
/// <summary>Arbitrary metadata from the parser (e.g., Mermaid node type).</summary>
3636
public Dictionary<string, object> Metadata { get; } = new();
3737

38+
/// <summary>
39+
/// Optional stereotype or annotation labels rendered above the node's primary title.
40+
/// </summary>
41+
public List<Label> Annotations { get; } = new();
42+
43+
/// <summary>
44+
/// Optional ordered compartments rendered within the node body.
45+
/// </summary>
46+
public List<NodeCompartment> Compartments { get; } = new();
47+
3848
/// <summary>Layout position computed by the layout engine.</summary>
3949
public double X { get; set; }
4050

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
namespace DiagramForge.Models;
2+
3+
/// <summary>
4+
/// An ordered section within a node, typically used for UML-style class compartments.
5+
/// </summary>
6+
public class NodeCompartment
7+
{
8+
public NodeCompartment()
9+
{
10+
}
11+
12+
public NodeCompartment(string? kind)
13+
{
14+
Kind = kind;
15+
}
16+
17+
public NodeCompartment(string? kind, IEnumerable<Label> lines)
18+
{
19+
Kind = kind;
20+
Lines.AddRange(lines);
21+
}
22+
23+
/// <summary>
24+
/// Optional compartment role metadata such as <c>title</c>, <c>attributes</c>, or <c>methods</c>.
25+
/// </summary>
26+
public string? Kind { get; set; }
27+
28+
/// <summary>
29+
/// Ordered label lines contained in the compartment.
30+
/// </summary>
31+
public List<Label> Lines { get; } = new();
32+
}

tests/DiagramForge.Tests/Models/DiagramModelTests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,46 @@ public void Node_LabelText_DefaultsToId_WhenSingleArgCtor()
7979
Assert.Equal("myId", node.Label.Text);
8080
}
8181

82+
[Fact]
83+
public void Node_NewClassDiagramCollections_DefaultToEmpty_WithoutChangingLegacyState()
84+
{
85+
var node = new Node("Customer", "Customer");
86+
87+
Assert.Equal("Customer", node.Label.Text);
88+
Assert.Empty(node.Annotations);
89+
Assert.Empty(node.Compartments);
90+
}
91+
92+
[Fact]
93+
public void Node_CompartmentsAndAnnotations_PreserveOrderAndContent()
94+
{
95+
var node = new Node("Order")
96+
{
97+
FillColor = "#ffeecc",
98+
};
99+
100+
node.Annotations.Add(new Label("<<entity>>"));
101+
node.Compartments.Add(new NodeCompartment("attributes", new[]
102+
{
103+
new Label("+Id: Guid"),
104+
new Label("+Status: string"),
105+
}));
106+
node.Compartments.Add(new NodeCompartment("methods", new[]
107+
{
108+
new Label("+Submit()"),
109+
}));
110+
111+
Assert.Single(node.Annotations);
112+
Assert.Equal("<<entity>>", node.Annotations[0].Text);
113+
Assert.Equal(2, node.Compartments.Count);
114+
Assert.Equal("attributes", node.Compartments[0].Kind);
115+
Assert.Equal("+Id: Guid", node.Compartments[0].Lines[0].Text);
116+
Assert.Equal("+Status: string", node.Compartments[0].Lines[1].Text);
117+
Assert.Equal("methods", node.Compartments[1].Kind);
118+
Assert.Equal("+Submit()", node.Compartments[1].Lines[0].Text);
119+
Assert.Equal("#ffeecc", node.FillColor);
120+
}
121+
82122
[Fact]
83123
public void Edge_DefaultArrowHead_IsArrow()
84124
{
@@ -95,6 +135,34 @@ public void Edge_DefaultLineStyle_IsSolid()
95135
Assert.Equal(EdgeLineStyle.Solid, edge.LineStyle);
96136
}
97137

138+
[Fact]
139+
public void Edge_EndLabels_DefaultToNull_WithoutAffectingCenterLabel()
140+
{
141+
var edge = new Edge("A", "B")
142+
{
143+
Label = new Label("relates to"),
144+
};
145+
146+
Assert.Equal("relates to", edge.Label?.Text);
147+
Assert.Null(edge.SourceLabel);
148+
Assert.Null(edge.TargetLabel);
149+
}
150+
151+
[Fact]
152+
public void Edge_CanStoreIndependentCenterSourceAndTargetLabels()
153+
{
154+
var edge = new Edge("Customer", "Ticket")
155+
{
156+
Label = new Label("owns"),
157+
SourceLabel = new Label("1"),
158+
TargetLabel = new Label("*"),
159+
};
160+
161+
Assert.Equal("owns", edge.Label?.Text);
162+
Assert.Equal("1", edge.SourceLabel?.Text);
163+
Assert.Equal("*", edge.TargetLabel?.Text);
164+
}
165+
98166
[Fact]
99167
public void LayoutHints_Defaults_AreReasonable()
100168
{

0 commit comments

Comments
 (0)