Complete ElkSharp document rendering cleanup and source decomposition

- Fix target-join (edge/4+edge/17): gateway face overflow redirect to left tip
- Fix under-node (edge/14,15,20): push-first corridor reroute instead of top corridor
- Fix boundary-slots (4->0): snap after gateway polish reordering
- Fix gateway corner diagonals (2->0): post-pipeline straightening pass
- Fix gateway interior adjacent: polygon-aware IsInsideNodeShapeInterior
- Fix gateway source face mismatch (2->0): per-edge redirect with lenient validation
- Fix gateway source scoring (5->0): per-edge scoring candidate application
- Fix edge-node crossing (1->0): push horizontal segment above blocking node
- Decompose 7 oversized files (~20K lines) into 55+ partials under 400 lines each
- Archive sprints 004 (document cleanup), 005 (decomposition), 007 (render speed)

All 44+ document-processing artifact assertions pass. Hybrid deterministic mode
documented as recommended path for LeftToRight layouts.

Tests verified: StraightExit 2/2, BoundarySlotOffenders 2/2, HybridDeterministicMode 3/3,
DocumentProcessingWorkflow artifact 1/1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-04-01 14:16:10 +03:00
parent 5fe42e171e
commit d04483560b
79 changed files with 18870 additions and 18061 deletions

View File

@@ -677,8 +677,12 @@ public partial class DocumentProcessingWorkflowRenderingTests
}
var adjacent = fromSource ? path[1] : path[^2];
return ElkShapeBoundaries.IsInsideNodeBoundingBoxInterior(
ToElkNode(node),
var elkNode = ToElkNode(node);
// Use the polygon-aware interior check for gateway shapes so that
// points outside the diamond polygon but inside the rectangular
// bounding box are not flagged as interior violations.
return ElkShapeBoundaries.IsInsideNodeShapeInterior(
elkNode,
new ElkPoint { X = adjacent.X, Y = adjacent.Y });
}

View File

@@ -2732,4 +2732,4 @@ public partial class ElkSharpEdgeRefinementTests
}
}
}
}

View File

@@ -0,0 +1,509 @@
using System.Reflection;
using System.Text.Json;
using FluentAssertions;
using NUnit.Framework;
using StellaOps.ElkSharp;
using StellaOps.Workflow.Abstractions;
namespace StellaOps.Workflow.Renderer.Tests;
public partial class ElkSharpEdgeRefinementTests
{
[Test]
[Property("Intent", "Operational")]
public void BoundarySlotHelpers_WhenLateCleanupLeavesSourceAndTargetOffLattice_ShouldSnapAssignedSlots()
{
var source = new ElkPositionedNode
{
Id = "source",
Label = "Internal Notification",
Kind = "TransportCall",
X = 3034,
Y = 653.352783203125,
Width = 208,
Height = 88,
};
var handled = new ElkPositionedNode
{
Id = "handled",
Label = "Set internalNotificationFailed",
Kind = "SetState",
X = 3406,
Y = 653.352783203125,
Width = 208,
Height = 88,
};
var hasRecipients = new ElkPositionedNode
{
Id = "hasRecipients",
Label = "Has Recipients",
Kind = "Decision",
X = 3778,
Y = 631.352783203125,
Width = 188,
Height = 132,
};
var sourceA = new ElkPositionedNode
{
Id = "source-a",
Label = "Source A",
Kind = "TransportCall",
X = 2100,
Y = 420,
Width = 208,
Height = 132,
};
var sourceB = new ElkPositionedNode
{
Id = "source-b",
Label = "Source B",
Kind = "TransportCall",
X = 2362,
Y = 330,
Width = 208,
Height = 132,
};
var target = new ElkPositionedNode
{
Id = "target",
Label = "Set batchGenerateFailed",
Kind = "SetState",
X = 2662,
Y = 479.4360656738281,
Width = 208,
Height = 88,
};
var sourceSlots = ElkBoundarySlots.BuildAssignedBoundarySlotCoordinates(source, "right", 2);
var targetSlots = ElkBoundarySlots.BuildAssignedBoundarySlotCoordinates(target, "left", 2);
var outgoingDirect = new ElkRoutedEdge
{
Id = "edge/26",
SourceNodeId = source.Id,
TargetNodeId = handled.Id,
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 3242, Y = 697.352783203125 },
EndPoint = new ElkPoint { X = 3406, Y = 697.352783203125 },
BendPoints = [],
},
],
};
var outgoingBranch = new ElkRoutedEdge
{
Id = "edge/27",
SourceNodeId = source.Id,
TargetNodeId = hasRecipients.Id,
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 3242, Y = 697.352783203125 },
EndPoint = new ElkPoint { X = 3806.7594033898904, Y = 717.5455557960269 },
BendPoints =
[
new ElkPoint { X = 3266, Y = 697.352783203125 },
new ElkPoint { X = 3266, Y = 828.1806263316762 },
],
},
],
};
var incomingDirect = new ElkRoutedEdge
{
Id = "edge/direct",
SourceNodeId = sourceA.Id,
TargetNodeId = target.Id,
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = sourceA.X + sourceA.Width, Y = 523.4360656738281 },
EndPoint = new ElkPoint { X = target.X, Y = 523.4360656738281 },
BendPoints = [],
},
],
};
var incomingElbow = new ElkRoutedEdge
{
Id = "edge/elbowed",
SourceNodeId = sourceB.Id,
TargetNodeId = target.Id,
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = sourceB.X + sourceB.Width, Y = 390 },
EndPoint = new ElkPoint { X = target.X, Y = targetSlots[1] },
BendPoints =
[
new ElkPoint { X = 2550, Y = 390 },
new ElkPoint { X = 2550, Y = targetSlots[1] },
],
},
],
};
var edges = new[] { outgoingDirect, outgoingBranch, incomingDirect, incomingElbow };
var nodes = new[] { source, handled, hasRecipients, sourceA, sourceB, target };
ElkEdgeRoutingScoring.CountBoundarySlotViolations(edges, nodes)
.Should().BeGreaterThan(0);
var repaired = ElkEdgePostProcessor.SnapBoundarySlotAssignments(edges, nodes, 53d);
ElkEdgeRoutingScoring.CountBoundarySlotViolations(repaired, nodes)
.Should().Be(0);
repaired.Single(edge => edge.Id == "edge/26").Sections.Single().StartPoint.Y.Should().Be(sourceSlots[0]);
repaired.Single(edge => edge.Id == "edge/27").Sections.Single().StartPoint.Y.Should().Be(sourceSlots[1]);
repaired.Single(edge => edge.Id == "edge/direct").Sections.Single().EndPoint.Y.Should().Be(targetSlots[0]);
repaired.Single(edge => edge.Id == "edge/elbowed").Sections.Single().EndPoint.Y.Should().Be(targetSlots[1]);
}
[Test]
[Property("Intent", "Operational")]
public void MixedNodeFaceHelpers_WhenIncomingAndOutgoingEdgesShareTheSameFaceLane_ShouldSeparateThem()
{
var process = new ElkPositionedNode
{
Id = "process",
Label = "Process Batch",
Kind = "Repeat",
X = 992,
Y = 247.181640625,
Width = 208,
Height = 88,
};
var validateSuccess = new ElkPositionedNode
{
Id = "validate",
Label = "Validate Success",
Kind = "Decision",
X = 3406,
Y = 225.181640625,
Width = 188,
Height = 132,
};
var join = new ElkPositionedNode
{
Id = "join",
Label = "Parallel Execution Join",
Kind = "Join",
X = 1290,
Y = 116.5908203125,
Width = 176,
Height = 124,
};
var incoming = new ElkRoutedEdge
{
Id = "edge/in",
SourceNodeId = validateSuccess.Id,
TargetNodeId = process.Id,
Label = "repeat while state.printInsisAttempt eq 0",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 3439.84, Y = 267.421640625 },
EndPoint = new ElkPoint { X = 1200, Y = 267.421640625 },
BendPoints = [],
},
],
};
var outgoing = new ElkRoutedEdge
{
Id = "edge/out",
SourceNodeId = process.Id,
TargetNodeId = join.Id,
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 1200, Y = 269.181640625 },
EndPoint = new ElkPoint { X = 1308.0475075276013, Y = 222.88924788024843 },
BendPoints =
[
new ElkPoint { X = 1282.5912611218437, Y = 269.181640625 },
],
},
],
};
var nodes = new[] { process, validateSuccess, join };
ElkEdgeRoutingScoring.CountSharedLaneViolations([incoming, outgoing], nodes)
.Should().Be(1);
ElkEdgeRoutingScoring.CountBoundarySlotViolations([incoming, outgoing], nodes)
.Should().BeGreaterThan(0);
var repaired = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts([incoming, outgoing], nodes, 53d);
repaired = ElkEdgePostProcessor.NormalizeBoundaryAngles(repaired, nodes);
repaired = ElkEdgePostProcessor.NormalizeSourceExitAngles(repaired, nodes);
ElkEdgeRoutingScoring.CountSharedLaneViolations(repaired, nodes)
.Should().Be(0);
ElkEdgeRoutingScoring.CountBoundarySlotViolations(repaired, nodes)
.Should().Be(0);
}
[Test]
[Property("Intent", "Operational")]
public void BoundarySlotHelpers_WhenGatewaySourceExitsShareARightFace_ShouldSnapToDistinctGatewaySlots()
{
var retryDecision = new ElkPositionedNode
{
Id = "retry",
Label = "Retry Decision",
Kind = "Decision",
X = 1976,
Y = 413.9718017578125,
Width = 188,
Height = 132,
};
var cooldownTimer = new ElkPositionedNode
{
Id = "cooldown",
Label = "Cooldown Timer",
Kind = "Timer",
X = 2290,
Y = 457.1265563964844,
Width = 208,
Height = 88,
};
var setBatchGenerateFailed = new ElkPositionedNode
{
Id = "failed",
Label = "Set batchGenerateFailed",
Kind = "SetState",
X = 2662,
Y = 479.4360656738281,
Width = 208,
Height = 88,
};
var whenNotExceeded = new ElkRoutedEdge
{
Id = "edge/8",
SourceNodeId = retryDecision.Id,
TargetNodeId = cooldownTimer.Id,
Label = "when notstate.printInsisAttempt gt 2",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2148.8381635762003, Y = 490.61734648090606 },
EndPoint = new ElkPoint { X = 2290, Y = 490.61734648090606 },
BendPoints = [],
},
],
};
var defaultExit = new ElkRoutedEdge
{
Id = "edge/9",
SourceNodeId = retryDecision.Id,
TargetNodeId = setBatchGenerateFailed.Id,
Label = "default",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2151.2098609190243, Y = 488.95211217636984 },
EndPoint = new ElkPoint { X = 2662, Y = 545.4360656738281 },
BendPoints =
[
new ElkPoint { X = 2172, Y = 488.95211217636984 },
new ElkPoint { X = 2172, Y = 545.4360656738281 },
],
},
],
};
var nodes = new[] { retryDecision, cooldownTimer, setBatchGenerateFailed };
ElkEdgeRoutingScoring.CountBoundarySlotViolations([whenNotExceeded, defaultExit], nodes)
.Should().BeGreaterThan(0);
var repaired = ElkEdgePostProcessor.SnapBoundarySlotAssignments([whenNotExceeded, defaultExit], nodes, 53d);
ElkEdgeRoutingScoring.CountBoundarySlotViolations(repaired, nodes)
.Should().Be(0);
var expectedSlots = ElkBoundarySlots.BuildAssignedBoundarySlotAxisCoordinates(retryDecision, "right", 2);
repaired.Select(edge => edge.Sections.Single().StartPoint.Y)
.OrderBy(value => value)
.Should().Equal(expectedSlots.OrderBy(value => value));
}
[Test]
[Property("Intent", "Operational")]
public void BoundarySlotHelpers_WhenGatewayRightFaceSlotMustRejoinSafeUpperLane_ShouldSnapWithoutCrossingBlocker()
{
var retryDecision = new ElkPositionedNode
{
Id = "retry",
Label = "Retry Decision",
Kind = "Decision",
X = 1976,
Y = 413.9718017578125,
Width = 188,
Height = 132,
};
var cooldownTimer = new ElkPositionedNode
{
Id = "cooldown",
Label = "Cooldown Timer",
Kind = "Timer",
X = 2290,
Y = 457.1265563964844,
Width = 208,
Height = 88,
};
var setBatchGenerateFailed = new ElkPositionedNode
{
Id = "failed",
Label = "Set batchGenerateFailed",
Kind = "SetState",
X = 2662,
Y = 479.4360656738281,
Width = 208,
Height = 88,
};
var whenNotExceeded = new ElkRoutedEdge
{
Id = "edge/8",
SourceNodeId = retryDecision.Id,
TargetNodeId = cooldownTimer.Id,
Label = "when notstate.printInsisAttempt gt 2",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2148.8381635762003, Y = 490.61734648090606 },
EndPoint = new ElkPoint { X = 2290, Y = 490.61734648090606 },
BendPoints = [],
},
],
};
var defaultExit = new ElkRoutedEdge
{
Id = "edge/9",
SourceNodeId = retryDecision.Id,
TargetNodeId = setBatchGenerateFailed.Id,
Label = "default",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2103.8402987865636, Y = 437.73227685820864 },
EndPoint = new ElkPoint { X = 2598.726806640625, Y = 479.4360656738281 },
BendPoints =
[
new ElkPoint { X = 2598.726806640625, Y = 437.73227685820864 },
],
},
],
};
var nodes = new[] { retryDecision, cooldownTimer, setBatchGenerateFailed };
ElkEdgeRoutingScoring.CountBoundarySlotViolations([whenNotExceeded, defaultExit], nodes)
.Should().BeGreaterThan(0);
var repaired = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
[whenNotExceeded, defaultExit],
nodes,
53d,
enforceAllNodeEndpoints: true);
var repairedEdge8Path = ExtractPath(repaired.Single(edge => edge.Id == "edge/8"));
var repairedEdge9Path = ExtractPath(repaired.Single(edge => edge.Id == "edge/9"));
var repairedDefaultPath = ExtractPath(repaired.Single(edge => edge.Id == "edge/9"));
var pathText = string.Join(" -> ", repairedDefaultPath.Select(point => $"({point.X:F3},{point.Y:F3})"));
ElkEdgeRoutingScoring.CountBoundarySlotViolations(repaired, nodes)
.Should().Be(0, pathText);
ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(repaired, nodes)
.Should().Be(0, pathText);
ElkEdgeRoutingScoring.CountEdgeNodeCrossings(repaired, nodes, null)
.Should().Be(0, pathText);
var expectedSlots = ElkBoundarySlots.BuildAssignedBoundarySlotAxisCoordinates(retryDecision, "right", 2)
.OrderBy(value => value)
.ToArray();
repairedDefaultPath[0].Y.Should().BeApproximately(expectedSlots[0], 0.5d, pathText);
repairedDefaultPath.Any(point => point.Y < cooldownTimer.Y - 0.5d)
.Should().BeTrue(pathText);
}
[Test]
[Property("Intent", "Operational")]
public void BoundarySlotHelpers_WhenStrictSnapSeesSingleRepeatCorridorExit_ShouldCenterTheDepartureSlot()
{
var repeatDecision = new ElkPositionedNode
{
Id = "repeat",
Label = "Repeat Decision",
Kind = "Decision",
X = 3000,
Y = 320,
Width = 188,
Height = 132,
};
var target = new ElkPositionedNode
{
Id = "target",
Label = "Internal Discussion",
Kind = "TransportCall",
X = 3420,
Y = 340,
Width = 208,
Height = 88,
};
var repeatReturn = new ElkRoutedEdge
{
Id = "edge/repeat-corridor",
SourceNodeId = repeatDecision.Id,
TargetNodeId = target.Id,
Label = "repeat while state.printInsisAttempt eq 0",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 3188, Y = 348 },
EndPoint = new ElkPoint { X = 3420, Y = 384 },
BendPoints =
[
new ElkPoint { X = 3228, Y = 348 },
new ElkPoint { X = 3228, Y = 240 },
new ElkPoint { X = 3420, Y = 240 },
],
},
],
};
var nodes = new[] { repeatDecision, target };
ElkEdgeRoutingScoring.CountBoundarySlotViolations([repeatReturn], nodes)
.Should().Be(1);
var repaired = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
[repeatReturn],
nodes,
53d,
enforceAllNodeEndpoints: true);
ElkEdgeRoutingScoring.CountBoundarySlotViolations(repaired, nodes)
.Should().Be(0);
var expectedStartY = ElkBoundarySlots.BuildAssignedBoundarySlotAxisCoordinates(repeatDecision, "right", 1).Single();
repaired.Single().Sections.Single().StartPoint.Y.Should().BeApproximately(expectedStartY, 0.5d);
}
}

View File

@@ -0,0 +1,392 @@
using System.Reflection;
using System.Text.Json;
using FluentAssertions;
using NUnit.Framework;
using StellaOps.ElkSharp;
using StellaOps.Workflow.Abstractions;
namespace StellaOps.Workflow.Renderer.Tests;
public partial class ElkSharpEdgeRefinementTests
{
[Test]
[Property("Intent", "Operational")]
public void GatewayBoundaryHelpers_WhenDominantAxisExitIsBlocked_ShouldNotCountAsBoundaryAngleViolation()
{
var source = new ElkPositionedNode
{
Id = "source",
Label = "Internal Notification",
Kind = "Decision",
X = 2557,
Y = 543.9360656738281,
Width = 188,
Height = 132,
};
var blocker = new ElkPositionedNode
{
Id = "blocker",
Label = "Set internalNotificationFailed",
Kind = "SetState",
X = 3206,
Y = 561.352783203125,
Width = 208,
Height = 88,
};
var target = new ElkPositionedNode
{
Id = "target",
Label = "Has Recipients",
Kind = "Decision",
X = 3578,
Y = 539.352783203125,
Width = 188,
Height = 132,
};
var edge = new ElkRoutedEdge
{
Id = "edge/25",
SourceNodeId = source.Id,
TargetNodeId = target.Id,
Label = "default",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2711.16, Y = 633.70 },
EndPoint = new ElkPoint { X = 3658.87, Y = 662.13 },
BendPoints =
[
new ElkPoint { X = 2745.41, Y = 682.48 },
new ElkPoint { X = 3658.87, Y = 682.48 },
],
},
],
};
var repaired = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry([edge], [source, blocker, target]);
var violations = ElkEdgeRoutingScoring.CountBadBoundaryAngles(repaired, [source, blocker, target]);
violations.Should().Be(0);
}
[Test]
[Property("Intent", "Operational")]
public void GatewayBoundaryHelpers_WhenGatewaySourceHasShorterClearExit_ShouldExposeOpportunityAndShortenPath()
{
var source = new ElkPositionedNode
{
Id = "source",
Label = "Internal Notification",
Kind = "Decision",
X = 2557,
Y = 543.9360656738281,
Width = 188,
Height = 132,
};
var target = new ElkPositionedNode
{
Id = "target",
Label = "Has Recipients",
Kind = "Decision",
X = 3578,
Y = 539.352783203125,
Width = 188,
Height = 132,
};
var edge = new ElkRoutedEdge
{
Id = "edge/25",
SourceNodeId = source.Id,
TargetNodeId = target.Id,
Label = "default",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2711.16, Y = 633.70 },
EndPoint = new ElkPoint { X = 3658.87, Y = 662.13 },
BendPoints =
[
new ElkPoint { X = 2745.41, Y = 682.48 },
new ElkPoint { X = 3658.87, Y = 682.48 },
],
},
],
};
var originalSection = edge.Sections.Single();
var originalPath = new List<ElkPoint> { originalSection.StartPoint };
originalPath.AddRange(originalSection.BendPoints);
originalPath.Add(originalSection.EndPoint);
ElkEdgePostProcessor.HasClearGatewaySourceDirectRepairOpportunity(
originalPath,
source,
[source, target],
edge.SourceNodeId,
edge.TargetNodeId).Should().BeTrue();
var repaired = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry([edge], [source, target]);
var section = repaired[0].Sections.Single();
var path = new List<ElkPoint> { section.StartPoint };
path.AddRange(section.BendPoints);
path.Add(section.EndPoint);
ComputePathLength(path).Should().BeLessThan(ComputePathLength(originalPath));
ElkEdgeRoutingScoring.CountBadBoundaryAngles(repaired, [source, target]).Should().Be(0);
static double ComputePathLength(IReadOnlyList<ElkPoint> points)
{
var length = 0d;
for (var i = 1; i < points.Count; i++)
{
var dx = points[i].X - points[i - 1].X;
var dy = points[i].Y - points[i - 1].Y;
length += Math.Sqrt((dx * dx) + (dy * dy));
}
return length;
}
}
[Test]
[Property("Intent", "Operational")]
public void GatewayBoundaryHelpers_WhenDecisionSourceNeedsVerticalStemForValidExit_ShouldKeepBoundaryRepair()
{
var source = new ElkPositionedNode
{
Id = "source",
Label = "Evaluate Conditions",
Kind = "Decision",
X = 2290,
Y = 32.25,
Width = 188,
Height = 132,
};
var target = new ElkPositionedNode
{
Id = "target",
Label = "Internal Notification",
Kind = "Decision",
X = 2662,
Y = 639.4360656738281,
Width = 188,
Height = 132,
};
var edge = new ElkRoutedEdge
{
Id = "edge/22",
SourceNodeId = source.Id,
TargetNodeId = target.Id,
Label = "when state.notificationHasBody",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2409.45679088221, Y = 172.25 },
EndPoint = new ElkPoint { X = 2693.4842064714944, Y = 683.3301334704383 },
BendPoints =
[
new ElkPoint { X = 2546.7272727272725, Y = 172.25 },
new ElkPoint { X = 2546.7272727272725, Y = 631.4360656738281 },
],
},
],
};
ElkEdgeRoutingScoring.CountBadBoundaryAngles([edge], [source, target]).Should().Be(1);
var repaired = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry([edge], [source, target]);
var section = repaired[0].Sections.Single();
var path = new List<ElkPoint> { section.StartPoint };
path.AddRange(section.BendPoints);
path.Add(section.EndPoint);
var originalFirstBend = edge.Sections.Single().BendPoints.First();
ElkEdgeRoutingScoring.CountBadBoundaryAngles(repaired, [source, target]).Should().Be(0);
ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(repaired, [source, target]).Should().Be(0);
path[1].Y.Should().BeGreaterThan(path[0].Y + 3d);
ElkEdgeRoutingGeometry.PointsEqual(path[1], originalFirstBend).Should().BeFalse();
}
[Test]
[Property("Intent", "Operational")]
public void GatewayBoundaryHelpers_WhenDecisionSourceVerticalStemWouldHitLocalBlocker_ShouldEscapeBeforeBlocker()
{
var source = new ElkPositionedNode
{
Id = "source",
Label = "Evaluate Conditions",
Kind = "Decision",
X = 2290,
Y = 32.25,
Width = 188,
Height = 132,
};
var blocker = new ElkPositionedNode
{
Id = "blocker",
Label = "Delay Notification",
Kind = "Timer",
X = 2290,
Y = 457.1265563964844,
Width = 208,
Height = 88,
};
var target = new ElkPositionedNode
{
Id = "target",
Label = "Internal Notification",
Kind = "Decision",
X = 2662,
Y = 639.4360656738281,
Width = 188,
Height = 132,
};
var edge = new ElkRoutedEdge
{
Id = "edge/22",
SourceNodeId = source.Id,
TargetNodeId = target.Id,
Label = "when state.notificationHasBody",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2409.45679088221, Y = 172.25 },
EndPoint = new ElkPoint { X = 2693.4842064714944, Y = 683.3301334704383 },
BendPoints =
[
new ElkPoint { X = 2546.7272727272725, Y = 172.25 },
new ElkPoint { X = 2546.7272727272725, Y = 631.4360656738281 },
],
},
],
};
var repaired = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry([edge], [source, blocker, target]);
var section = repaired[0].Sections.Single();
var path = new List<ElkPoint> { section.StartPoint };
path.AddRange(section.BendPoints);
path.Add(section.EndPoint);
TestContext.Out.WriteLine(
$"{string.Join(" -> ", path.Select(point => $"({point.X:F3},{point.Y:F3})"))} "
+ $"boundary={ElkEdgeRoutingScoring.CountBadBoundaryAngles(repaired, [source, blocker, target])} "
+ $"gateway-source={ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(repaired, [source, blocker, target])}");
ElkEdgeRoutingScoring.CountBadBoundaryAngles(repaired, [source, blocker, target]).Should().Be(0);
ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(repaired, [source, blocker, target]).Should().Be(0);
path[1].Y.Should().BeGreaterThan(path[0].Y + 3d);
path.Should().Contain(point => point.X > blocker.X + blocker.Width + 8d && point.Y < blocker.Y - 0.5d);
CrossesRectObstacle(path, blocker).Should().BeFalse();
static bool CrossesRectObstacle(IReadOnlyList<ElkPoint> polyline, ElkPositionedNode node)
{
const double tolerance = 0.5d;
for (var i = 0; i < polyline.Count - 1; i++)
{
var start = polyline[i];
var end = polyline[i + 1];
if (Math.Abs(start.Y - end.Y) <= tolerance)
{
if (start.Y > node.Y + tolerance
&& start.Y < node.Y + node.Height - tolerance
&& Math.Max(start.X, end.X) > node.X + tolerance
&& Math.Min(start.X, end.X) < node.X + node.Width - tolerance)
{
return true;
}
}
else if (Math.Abs(start.X - end.X) <= tolerance)
{
if (start.X > node.X + tolerance
&& start.X < node.X + node.Width - tolerance
&& Math.Max(start.Y, end.Y) > node.Y + tolerance
&& Math.Min(start.Y, end.Y) < node.Y + node.Height - tolerance)
{
return true;
}
}
}
return false;
}
}
[Test]
[Property("Intent", "Operational")]
public void GatewayBoundaryHelpers_WhenUpperGatewayArrivalsCollapseOntoSameLane_ShouldCountJoinAndSharedLaneViolations()
{
var target = new ElkPositionedNode
{
Id = "target",
Label = "Has Recipients",
Kind = "Decision",
X = 3578,
Y = 539.352783203125,
Width = 188,
Height = 132,
};
var leftArrival = new ElkRoutedEdge
{
Id = "edge/25",
SourceNodeId = "source-a",
TargetNodeId = target.Id,
Label = "default",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2738.472294662938, Y = 605.352783203125 },
EndPoint = new ElkPoint { X = 3586.8910474656404, Y = 599.1101328549094 },
BendPoints =
[
new ElkPoint { X = 2738.472294662938, Y = 535.9360656738281 },
new ElkPoint { X = 3586.8910474656404, Y = 535.9360656738281 },
],
},
],
};
var rightArrival = new ElkRoutedEdge
{
Id = "edge/28",
SourceNodeId = "source-b",
TargetNodeId = target.Id,
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 3414, Y = 605.352783203125 },
EndPoint = new ElkPoint { X = 3682.79150671785, Y = 546.9297985582112 },
BendPoints =
[
new ElkPoint { X = 3438, Y = 605.352783203125 },
new ElkPoint { X = 3438, Y = 532.8054790069142 },
new ElkPoint { X = 3682.79150671785, Y = 532.8054790069142 },
],
},
],
};
var nodes = new[] { target };
ElkEdgeRoutingScoring.CountTargetApproachJoinViolations([leftArrival, rightArrival], nodes)
.Should().Be(1);
ElkEdgeRoutingScoring.CountSharedLaneViolations([leftArrival, rightArrival], nodes)
.Should().Be(1);
}
}

View File

@@ -11,385 +11,6 @@ namespace StellaOps.Workflow.Renderer.Tests;
public partial class ElkSharpEdgeRefinementTests
{
[Test]
[Property("Intent", "Operational")]
public void GatewayBoundaryHelpers_WhenDominantAxisExitIsBlocked_ShouldNotCountAsBoundaryAngleViolation()
{
var source = new ElkPositionedNode
{
Id = "source",
Label = "Internal Notification",
Kind = "Decision",
X = 2557,
Y = 543.9360656738281,
Width = 188,
Height = 132,
};
var blocker = new ElkPositionedNode
{
Id = "blocker",
Label = "Set internalNotificationFailed",
Kind = "SetState",
X = 3206,
Y = 561.352783203125,
Width = 208,
Height = 88,
};
var target = new ElkPositionedNode
{
Id = "target",
Label = "Has Recipients",
Kind = "Decision",
X = 3578,
Y = 539.352783203125,
Width = 188,
Height = 132,
};
var edge = new ElkRoutedEdge
{
Id = "edge/25",
SourceNodeId = source.Id,
TargetNodeId = target.Id,
Label = "default",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2711.16, Y = 633.70 },
EndPoint = new ElkPoint { X = 3658.87, Y = 662.13 },
BendPoints =
[
new ElkPoint { X = 2745.41, Y = 682.48 },
new ElkPoint { X = 3658.87, Y = 682.48 },
],
},
],
};
var repaired = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry([edge], [source, blocker, target]);
var violations = ElkEdgeRoutingScoring.CountBadBoundaryAngles(repaired, [source, blocker, target]);
violations.Should().Be(0);
}
[Test]
[Property("Intent", "Operational")]
public void GatewayBoundaryHelpers_WhenGatewaySourceHasShorterClearExit_ShouldExposeOpportunityAndShortenPath()
{
var source = new ElkPositionedNode
{
Id = "source",
Label = "Internal Notification",
Kind = "Decision",
X = 2557,
Y = 543.9360656738281,
Width = 188,
Height = 132,
};
var target = new ElkPositionedNode
{
Id = "target",
Label = "Has Recipients",
Kind = "Decision",
X = 3578,
Y = 539.352783203125,
Width = 188,
Height = 132,
};
var edge = new ElkRoutedEdge
{
Id = "edge/25",
SourceNodeId = source.Id,
TargetNodeId = target.Id,
Label = "default",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2711.16, Y = 633.70 },
EndPoint = new ElkPoint { X = 3658.87, Y = 662.13 },
BendPoints =
[
new ElkPoint { X = 2745.41, Y = 682.48 },
new ElkPoint { X = 3658.87, Y = 682.48 },
],
},
],
};
var originalSection = edge.Sections.Single();
var originalPath = new List<ElkPoint> { originalSection.StartPoint };
originalPath.AddRange(originalSection.BendPoints);
originalPath.Add(originalSection.EndPoint);
ElkEdgePostProcessor.HasClearGatewaySourceDirectRepairOpportunity(
originalPath,
source,
[source, target],
edge.SourceNodeId,
edge.TargetNodeId).Should().BeTrue();
var repaired = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry([edge], [source, target]);
var section = repaired[0].Sections.Single();
var path = new List<ElkPoint> { section.StartPoint };
path.AddRange(section.BendPoints);
path.Add(section.EndPoint);
ComputePathLength(path).Should().BeLessThan(ComputePathLength(originalPath));
ElkEdgeRoutingScoring.CountBadBoundaryAngles(repaired, [source, target]).Should().Be(0);
static double ComputePathLength(IReadOnlyList<ElkPoint> points)
{
var length = 0d;
for (var i = 1; i < points.Count; i++)
{
var dx = points[i].X - points[i - 1].X;
var dy = points[i].Y - points[i - 1].Y;
length += Math.Sqrt((dx * dx) + (dy * dy));
}
return length;
}
}
[Test]
[Property("Intent", "Operational")]
public void GatewayBoundaryHelpers_WhenDecisionSourceNeedsVerticalStemForValidExit_ShouldKeepBoundaryRepair()
{
var source = new ElkPositionedNode
{
Id = "source",
Label = "Evaluate Conditions",
Kind = "Decision",
X = 2290,
Y = 32.25,
Width = 188,
Height = 132,
};
var target = new ElkPositionedNode
{
Id = "target",
Label = "Internal Notification",
Kind = "Decision",
X = 2662,
Y = 639.4360656738281,
Width = 188,
Height = 132,
};
var edge = new ElkRoutedEdge
{
Id = "edge/22",
SourceNodeId = source.Id,
TargetNodeId = target.Id,
Label = "when state.notificationHasBody",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2409.45679088221, Y = 172.25 },
EndPoint = new ElkPoint { X = 2693.4842064714944, Y = 683.3301334704383 },
BendPoints =
[
new ElkPoint { X = 2546.7272727272725, Y = 172.25 },
new ElkPoint { X = 2546.7272727272725, Y = 631.4360656738281 },
],
},
],
};
ElkEdgeRoutingScoring.CountBadBoundaryAngles([edge], [source, target]).Should().Be(1);
var repaired = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry([edge], [source, target]);
var section = repaired[0].Sections.Single();
var path = new List<ElkPoint> { section.StartPoint };
path.AddRange(section.BendPoints);
path.Add(section.EndPoint);
var originalFirstBend = edge.Sections.Single().BendPoints.First();
ElkEdgeRoutingScoring.CountBadBoundaryAngles(repaired, [source, target]).Should().Be(0);
ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(repaired, [source, target]).Should().Be(0);
path[1].Y.Should().BeGreaterThan(path[0].Y + 3d);
ElkEdgeRoutingGeometry.PointsEqual(path[1], originalFirstBend).Should().BeFalse();
}
[Test]
[Property("Intent", "Operational")]
public void GatewayBoundaryHelpers_WhenDecisionSourceVerticalStemWouldHitLocalBlocker_ShouldEscapeBeforeBlocker()
{
var source = new ElkPositionedNode
{
Id = "source",
Label = "Evaluate Conditions",
Kind = "Decision",
X = 2290,
Y = 32.25,
Width = 188,
Height = 132,
};
var blocker = new ElkPositionedNode
{
Id = "blocker",
Label = "Delay Notification",
Kind = "Timer",
X = 2290,
Y = 457.1265563964844,
Width = 208,
Height = 88,
};
var target = new ElkPositionedNode
{
Id = "target",
Label = "Internal Notification",
Kind = "Decision",
X = 2662,
Y = 639.4360656738281,
Width = 188,
Height = 132,
};
var edge = new ElkRoutedEdge
{
Id = "edge/22",
SourceNodeId = source.Id,
TargetNodeId = target.Id,
Label = "when state.notificationHasBody",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2409.45679088221, Y = 172.25 },
EndPoint = new ElkPoint { X = 2693.4842064714944, Y = 683.3301334704383 },
BendPoints =
[
new ElkPoint { X = 2546.7272727272725, Y = 172.25 },
new ElkPoint { X = 2546.7272727272725, Y = 631.4360656738281 },
],
},
],
};
var repaired = ElkEdgePostProcessor.FinalizeGatewayBoundaryGeometry([edge], [source, blocker, target]);
var section = repaired[0].Sections.Single();
var path = new List<ElkPoint> { section.StartPoint };
path.AddRange(section.BendPoints);
path.Add(section.EndPoint);
TestContext.Out.WriteLine(
$"{string.Join(" -> ", path.Select(point => $"({point.X:F3},{point.Y:F3})"))} "
+ $"boundary={ElkEdgeRoutingScoring.CountBadBoundaryAngles(repaired, [source, blocker, target])} "
+ $"gateway-source={ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(repaired, [source, blocker, target])}");
ElkEdgeRoutingScoring.CountBadBoundaryAngles(repaired, [source, blocker, target]).Should().Be(0);
ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(repaired, [source, blocker, target]).Should().Be(0);
path[1].Y.Should().BeGreaterThan(path[0].Y + 3d);
path.Should().Contain(point => point.X > blocker.X + blocker.Width + 8d && point.Y < blocker.Y - 0.5d);
CrossesRectObstacle(path, blocker).Should().BeFalse();
static bool CrossesRectObstacle(IReadOnlyList<ElkPoint> polyline, ElkPositionedNode node)
{
const double tolerance = 0.5d;
for (var i = 0; i < polyline.Count - 1; i++)
{
var start = polyline[i];
var end = polyline[i + 1];
if (Math.Abs(start.Y - end.Y) <= tolerance)
{
if (start.Y > node.Y + tolerance
&& start.Y < node.Y + node.Height - tolerance
&& Math.Max(start.X, end.X) > node.X + tolerance
&& Math.Min(start.X, end.X) < node.X + node.Width - tolerance)
{
return true;
}
}
else if (Math.Abs(start.X - end.X) <= tolerance)
{
if (start.X > node.X + tolerance
&& start.X < node.X + node.Width - tolerance
&& Math.Max(start.Y, end.Y) > node.Y + tolerance
&& Math.Min(start.Y, end.Y) < node.Y + node.Height - tolerance)
{
return true;
}
}
}
return false;
}
}
[Test]
[Property("Intent", "Operational")]
public void GatewayBoundaryHelpers_WhenUpperGatewayArrivalsCollapseOntoSameLane_ShouldCountJoinAndSharedLaneViolations()
{
var target = new ElkPositionedNode
{
Id = "target",
Label = "Has Recipients",
Kind = "Decision",
X = 3578,
Y = 539.352783203125,
Width = 188,
Height = 132,
};
var leftArrival = new ElkRoutedEdge
{
Id = "edge/25",
SourceNodeId = "source-a",
TargetNodeId = target.Id,
Label = "default",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2738.472294662938, Y = 605.352783203125 },
EndPoint = new ElkPoint { X = 3586.8910474656404, Y = 599.1101328549094 },
BendPoints =
[
new ElkPoint { X = 2738.472294662938, Y = 535.9360656738281 },
new ElkPoint { X = 3586.8910474656404, Y = 535.9360656738281 },
],
},
],
};
var rightArrival = new ElkRoutedEdge
{
Id = "edge/28",
SourceNodeId = "source-b",
TargetNodeId = target.Id,
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 3414, Y = 605.352783203125 },
EndPoint = new ElkPoint { X = 3682.79150671785, Y = 546.9297985582112 },
BendPoints =
[
new ElkPoint { X = 3438, Y = 605.352783203125 },
new ElkPoint { X = 3438, Y = 532.8054790069142 },
new ElkPoint { X = 3682.79150671785, Y = 532.8054790069142 },
],
},
],
};
var nodes = new[] { target };
ElkEdgeRoutingScoring.CountTargetApproachJoinViolations([leftArrival, rightArrival], nodes)
.Should().Be(1);
ElkEdgeRoutingScoring.CountSharedLaneViolations([leftArrival, rightArrival], nodes)
.Should().Be(1);
}
[Test]
[Property("Intent", "Operational")]
public void SourceDepartureHelpers_WhenOutgoingEdgesShareTheSameDepartureLane_ShouldSpreadOnlyTheConflictingPeer()
@@ -496,500 +117,4 @@ public partial class ElkSharpEdgeRefinementTests
repairedBranching.StartPoint.Y.Should().Be(assignedSourceSlots[1]);
repairedBranchingPoints.Should().NotBeEquivalentTo(originalBranchingPoints);
}
[Test]
[Property("Intent", "Operational")]
public void BoundarySlotHelpers_WhenLateCleanupLeavesSourceAndTargetOffLattice_ShouldSnapAssignedSlots()
{
var source = new ElkPositionedNode
{
Id = "source",
Label = "Internal Notification",
Kind = "TransportCall",
X = 3034,
Y = 653.352783203125,
Width = 208,
Height = 88,
};
var handled = new ElkPositionedNode
{
Id = "handled",
Label = "Set internalNotificationFailed",
Kind = "SetState",
X = 3406,
Y = 653.352783203125,
Width = 208,
Height = 88,
};
var hasRecipients = new ElkPositionedNode
{
Id = "hasRecipients",
Label = "Has Recipients",
Kind = "Decision",
X = 3778,
Y = 631.352783203125,
Width = 188,
Height = 132,
};
var sourceA = new ElkPositionedNode
{
Id = "source-a",
Label = "Source A",
Kind = "TransportCall",
X = 2100,
Y = 420,
Width = 208,
Height = 132,
};
var sourceB = new ElkPositionedNode
{
Id = "source-b",
Label = "Source B",
Kind = "TransportCall",
X = 2362,
Y = 330,
Width = 208,
Height = 132,
};
var target = new ElkPositionedNode
{
Id = "target",
Label = "Set batchGenerateFailed",
Kind = "SetState",
X = 2662,
Y = 479.4360656738281,
Width = 208,
Height = 88,
};
var sourceSlots = ElkBoundarySlots.BuildAssignedBoundarySlotCoordinates(source, "right", 2);
var targetSlots = ElkBoundarySlots.BuildAssignedBoundarySlotCoordinates(target, "left", 2);
var outgoingDirect = new ElkRoutedEdge
{
Id = "edge/26",
SourceNodeId = source.Id,
TargetNodeId = handled.Id,
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 3242, Y = 697.352783203125 },
EndPoint = new ElkPoint { X = 3406, Y = 697.352783203125 },
BendPoints = [],
},
],
};
var outgoingBranch = new ElkRoutedEdge
{
Id = "edge/27",
SourceNodeId = source.Id,
TargetNodeId = hasRecipients.Id,
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 3242, Y = 697.352783203125 },
EndPoint = new ElkPoint { X = 3806.7594033898904, Y = 717.5455557960269 },
BendPoints =
[
new ElkPoint { X = 3266, Y = 697.352783203125 },
new ElkPoint { X = 3266, Y = 828.1806263316762 },
],
},
],
};
var incomingDirect = new ElkRoutedEdge
{
Id = "edge/direct",
SourceNodeId = sourceA.Id,
TargetNodeId = target.Id,
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = sourceA.X + sourceA.Width, Y = 523.4360656738281 },
EndPoint = new ElkPoint { X = target.X, Y = 523.4360656738281 },
BendPoints = [],
},
],
};
var incomingElbow = new ElkRoutedEdge
{
Id = "edge/elbowed",
SourceNodeId = sourceB.Id,
TargetNodeId = target.Id,
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = sourceB.X + sourceB.Width, Y = 390 },
EndPoint = new ElkPoint { X = target.X, Y = targetSlots[1] },
BendPoints =
[
new ElkPoint { X = 2550, Y = 390 },
new ElkPoint { X = 2550, Y = targetSlots[1] },
],
},
],
};
var edges = new[] { outgoingDirect, outgoingBranch, incomingDirect, incomingElbow };
var nodes = new[] { source, handled, hasRecipients, sourceA, sourceB, target };
ElkEdgeRoutingScoring.CountBoundarySlotViolations(edges, nodes)
.Should().BeGreaterThan(0);
var repaired = ElkEdgePostProcessor.SnapBoundarySlotAssignments(edges, nodes, 53d);
ElkEdgeRoutingScoring.CountBoundarySlotViolations(repaired, nodes)
.Should().Be(0);
repaired.Single(edge => edge.Id == "edge/26").Sections.Single().StartPoint.Y.Should().Be(sourceSlots[0]);
repaired.Single(edge => edge.Id == "edge/27").Sections.Single().StartPoint.Y.Should().Be(sourceSlots[1]);
repaired.Single(edge => edge.Id == "edge/direct").Sections.Single().EndPoint.Y.Should().Be(targetSlots[0]);
repaired.Single(edge => edge.Id == "edge/elbowed").Sections.Single().EndPoint.Y.Should().Be(targetSlots[1]);
}
[Test]
[Property("Intent", "Operational")]
public void MixedNodeFaceHelpers_WhenIncomingAndOutgoingEdgesShareTheSameFaceLane_ShouldSeparateThem()
{
var process = new ElkPositionedNode
{
Id = "process",
Label = "Process Batch",
Kind = "Repeat",
X = 992,
Y = 247.181640625,
Width = 208,
Height = 88,
};
var validateSuccess = new ElkPositionedNode
{
Id = "validate",
Label = "Validate Success",
Kind = "Decision",
X = 3406,
Y = 225.181640625,
Width = 188,
Height = 132,
};
var join = new ElkPositionedNode
{
Id = "join",
Label = "Parallel Execution Join",
Kind = "Join",
X = 1290,
Y = 116.5908203125,
Width = 176,
Height = 124,
};
var incoming = new ElkRoutedEdge
{
Id = "edge/in",
SourceNodeId = validateSuccess.Id,
TargetNodeId = process.Id,
Label = "repeat while state.printInsisAttempt eq 0",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 3439.84, Y = 267.421640625 },
EndPoint = new ElkPoint { X = 1200, Y = 267.421640625 },
BendPoints = [],
},
],
};
var outgoing = new ElkRoutedEdge
{
Id = "edge/out",
SourceNodeId = process.Id,
TargetNodeId = join.Id,
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 1200, Y = 269.181640625 },
EndPoint = new ElkPoint { X = 1308.0475075276013, Y = 222.88924788024843 },
BendPoints =
[
new ElkPoint { X = 1282.5912611218437, Y = 269.181640625 },
],
},
],
};
var nodes = new[] { process, validateSuccess, join };
ElkEdgeRoutingScoring.CountSharedLaneViolations([incoming, outgoing], nodes)
.Should().Be(1);
ElkEdgeRoutingScoring.CountBoundarySlotViolations([incoming, outgoing], nodes)
.Should().BeGreaterThan(0);
var repaired = ElkEdgePostProcessor.SeparateMixedNodeFaceLaneConflicts([incoming, outgoing], nodes, 53d);
repaired = ElkEdgePostProcessor.NormalizeBoundaryAngles(repaired, nodes);
repaired = ElkEdgePostProcessor.NormalizeSourceExitAngles(repaired, nodes);
ElkEdgeRoutingScoring.CountSharedLaneViolations(repaired, nodes)
.Should().Be(0);
ElkEdgeRoutingScoring.CountBoundarySlotViolations(repaired, nodes)
.Should().Be(0);
}
[Test]
[Property("Intent", "Operational")]
public void BoundarySlotHelpers_WhenGatewaySourceExitsShareARightFace_ShouldSnapToDistinctGatewaySlots()
{
var retryDecision = new ElkPositionedNode
{
Id = "retry",
Label = "Retry Decision",
Kind = "Decision",
X = 1976,
Y = 413.9718017578125,
Width = 188,
Height = 132,
};
var cooldownTimer = new ElkPositionedNode
{
Id = "cooldown",
Label = "Cooldown Timer",
Kind = "Timer",
X = 2290,
Y = 457.1265563964844,
Width = 208,
Height = 88,
};
var setBatchGenerateFailed = new ElkPositionedNode
{
Id = "failed",
Label = "Set batchGenerateFailed",
Kind = "SetState",
X = 2662,
Y = 479.4360656738281,
Width = 208,
Height = 88,
};
var whenNotExceeded = new ElkRoutedEdge
{
Id = "edge/8",
SourceNodeId = retryDecision.Id,
TargetNodeId = cooldownTimer.Id,
Label = "when notstate.printInsisAttempt gt 2",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2148.8381635762003, Y = 490.61734648090606 },
EndPoint = new ElkPoint { X = 2290, Y = 490.61734648090606 },
BendPoints = [],
},
],
};
var defaultExit = new ElkRoutedEdge
{
Id = "edge/9",
SourceNodeId = retryDecision.Id,
TargetNodeId = setBatchGenerateFailed.Id,
Label = "default",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2151.2098609190243, Y = 488.95211217636984 },
EndPoint = new ElkPoint { X = 2662, Y = 545.4360656738281 },
BendPoints =
[
new ElkPoint { X = 2172, Y = 488.95211217636984 },
new ElkPoint { X = 2172, Y = 545.4360656738281 },
],
},
],
};
var nodes = new[] { retryDecision, cooldownTimer, setBatchGenerateFailed };
ElkEdgeRoutingScoring.CountBoundarySlotViolations([whenNotExceeded, defaultExit], nodes)
.Should().BeGreaterThan(0);
var repaired = ElkEdgePostProcessor.SnapBoundarySlotAssignments([whenNotExceeded, defaultExit], nodes, 53d);
ElkEdgeRoutingScoring.CountBoundarySlotViolations(repaired, nodes)
.Should().Be(0);
var expectedSlots = ElkBoundarySlots.BuildAssignedBoundarySlotAxisCoordinates(retryDecision, "right", 2);
repaired.Select(edge => edge.Sections.Single().StartPoint.Y)
.OrderBy(value => value)
.Should().Equal(expectedSlots.OrderBy(value => value));
}
[Test]
[Property("Intent", "Operational")]
public void BoundarySlotHelpers_WhenGatewayRightFaceSlotMustRejoinSafeUpperLane_ShouldSnapWithoutCrossingBlocker()
{
var retryDecision = new ElkPositionedNode
{
Id = "retry",
Label = "Retry Decision",
Kind = "Decision",
X = 1976,
Y = 413.9718017578125,
Width = 188,
Height = 132,
};
var cooldownTimer = new ElkPositionedNode
{
Id = "cooldown",
Label = "Cooldown Timer",
Kind = "Timer",
X = 2290,
Y = 457.1265563964844,
Width = 208,
Height = 88,
};
var setBatchGenerateFailed = new ElkPositionedNode
{
Id = "failed",
Label = "Set batchGenerateFailed",
Kind = "SetState",
X = 2662,
Y = 479.4360656738281,
Width = 208,
Height = 88,
};
var whenNotExceeded = new ElkRoutedEdge
{
Id = "edge/8",
SourceNodeId = retryDecision.Id,
TargetNodeId = cooldownTimer.Id,
Label = "when notstate.printInsisAttempt gt 2",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2148.8381635762003, Y = 490.61734648090606 },
EndPoint = new ElkPoint { X = 2290, Y = 490.61734648090606 },
BendPoints = [],
},
],
};
var defaultExit = new ElkRoutedEdge
{
Id = "edge/9",
SourceNodeId = retryDecision.Id,
TargetNodeId = setBatchGenerateFailed.Id,
Label = "default",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 2103.8402987865636, Y = 437.73227685820864 },
EndPoint = new ElkPoint { X = 2598.726806640625, Y = 479.4360656738281 },
BendPoints =
[
new ElkPoint { X = 2598.726806640625, Y = 437.73227685820864 },
],
},
],
};
var nodes = new[] { retryDecision, cooldownTimer, setBatchGenerateFailed };
ElkEdgeRoutingScoring.CountBoundarySlotViolations([whenNotExceeded, defaultExit], nodes)
.Should().BeGreaterThan(0);
var repaired = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
[whenNotExceeded, defaultExit],
nodes,
53d,
enforceAllNodeEndpoints: true);
var repairedEdge8Path = ExtractPath(repaired.Single(edge => edge.Id == "edge/8"));
var repairedEdge9Path = ExtractPath(repaired.Single(edge => edge.Id == "edge/9"));
var repairedDefaultPath = ExtractPath(repaired.Single(edge => edge.Id == "edge/9"));
var pathText = string.Join(" -> ", repairedDefaultPath.Select(point => $"({point.X:F3},{point.Y:F3})"));
ElkEdgeRoutingScoring.CountBoundarySlotViolations(repaired, nodes)
.Should().Be(0, pathText);
ElkEdgeRoutingScoring.CountGatewaySourceExitViolations(repaired, nodes)
.Should().Be(0, pathText);
ElkEdgeRoutingScoring.CountEdgeNodeCrossings(repaired, nodes, null)
.Should().Be(0, pathText);
var expectedSlots = ElkBoundarySlots.BuildAssignedBoundarySlotAxisCoordinates(retryDecision, "right", 2)
.OrderBy(value => value)
.ToArray();
repairedDefaultPath[0].Y.Should().BeApproximately(expectedSlots[0], 0.5d, pathText);
repairedDefaultPath.Any(point => point.Y < cooldownTimer.Y - 0.5d)
.Should().BeTrue(pathText);
}
[Test]
[Property("Intent", "Operational")]
public void BoundarySlotHelpers_WhenStrictSnapSeesSingleRepeatCorridorExit_ShouldCenterTheDepartureSlot()
{
var repeatDecision = new ElkPositionedNode
{
Id = "repeat",
Label = "Repeat Decision",
Kind = "Decision",
X = 3000,
Y = 320,
Width = 188,
Height = 132,
};
var target = new ElkPositionedNode
{
Id = "target",
Label = "Internal Discussion",
Kind = "TransportCall",
X = 3420,
Y = 340,
Width = 208,
Height = 88,
};
var repeatReturn = new ElkRoutedEdge
{
Id = "edge/repeat-corridor",
SourceNodeId = repeatDecision.Id,
TargetNodeId = target.Id,
Label = "repeat while state.printInsisAttempt eq 0",
Sections =
[
new ElkEdgeSection
{
StartPoint = new ElkPoint { X = 3188, Y = 348 },
EndPoint = new ElkPoint { X = 3420, Y = 384 },
BendPoints =
[
new ElkPoint { X = 3228, Y = 348 },
new ElkPoint { X = 3228, Y = 240 },
new ElkPoint { X = 3420, Y = 240 },
],
},
],
};
var nodes = new[] { repeatDecision, target };
ElkEdgeRoutingScoring.CountBoundarySlotViolations([repeatReturn], nodes)
.Should().Be(1);
var repaired = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
[repeatReturn],
nodes,
53d,
enforceAllNodeEndpoints: true);
ElkEdgeRoutingScoring.CountBoundarySlotViolations(repaired, nodes)
.Should().Be(0);
var expectedStartY = ElkBoundarySlots.BuildAssignedBoundarySlotAxisCoordinates(repeatDecision, "right", 1).Single();
repaired.Single().Sections.Single().StartPoint.Y.Should().BeApproximately(expectedStartY, 0.5d);
}
}