This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch 
CAMEL-23631-route-diagram-highlight-error-path
in repository https://gitbox.apache.org/repos/asf/camel.git

commit ee9ac73c91e975f1fd15c01bba0f81d7ca2ed97a
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed May 27 21:48:07 2026 +0200

    CAMEL-23631: Add unit tests for route diagram highlight feature
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../org/apache/camel/diagram/RouteDiagramTest.java | 328 +++++++++++++++++++++
 1 file changed, 328 insertions(+)

diff --git 
a/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
 
b/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
index 0684c1430754..829d711b4cf4 100644
--- 
a/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
+++ 
b/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
@@ -19,7 +19,10 @@ package org.apache.camel.diagram;
 import java.awt.Color;
 import java.awt.image.BufferedImage;
 import java.util.List;
+import java.util.Set;
 
+import org.apache.camel.diagram.RouteDiagramHelper.HighlightInfo;
+import org.apache.camel.diagram.RouteDiagramHelper.HighlightStyle;
 import org.apache.camel.diagram.RouteDiagramLayoutEngine.LayoutNode;
 import org.apache.camel.diagram.RouteDiagramLayoutEngine.LayoutRoute;
 import org.apache.camel.diagram.RouteDiagramLayoutEngine.NodeInfo;
@@ -30,6 +33,7 @@ import 
org.apache.camel.diagram.RouteDiagramRenderer.DiagramColors;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -1130,6 +1134,313 @@ class RouteDiagramTest {
         assertTrue(result.contains("log:a"));
     }
 
+    // --- Highlight tests ---
+
+    @Test
+    void testParseMessageHistorySuccess() {
+        String[] history = {
+                "route1[from1] (0 ms)",
+                "route1[log1] (5 ms)",
+                "route2[to1] (10 ms)"
+        };
+        HighlightInfo info = RouteDiagramHelper.parseMessageHistory(history, 
HighlightStyle.SUCCESS);
+
+        assertEquals(Set.of("from1", "log1", "to1"), info.getNodeIds());
+        assertEquals(List.of("route1", "route2"), info.getRouteOrder());
+        assertEquals(HighlightStyle.SUCCESS, info.getStyle());
+    }
+
+    @Test
+    void testParseMessageHistoryFail() {
+        String[] history = {
+                "route1[node1] (0 ms)",
+                "route1[node2] (5 ms)"
+        };
+        HighlightInfo info = RouteDiagramHelper.parseMessageHistory(history, 
HighlightStyle.FAIL);
+
+        assertEquals(Set.of("node1", "node2"), info.getNodeIds());
+        assertEquals(HighlightStyle.FAIL, info.getStyle());
+    }
+
+    @Test
+    void testParseMessageHistoryNull() {
+        HighlightInfo info = RouteDiagramHelper.parseMessageHistory(null, 
HighlightStyle.SUCCESS);
+
+        assertTrue(info.getNodeIds().isEmpty());
+        assertTrue(info.getRouteOrder().isEmpty());
+    }
+
+    @Test
+    void testParseMessageHistoryEmpty() {
+        HighlightInfo info = RouteDiagramHelper.parseMessageHistory(new 
String[0], HighlightStyle.SUCCESS);
+
+        assertTrue(info.getNodeIds().isEmpty());
+        assertTrue(info.getRouteOrder().isEmpty());
+    }
+
+    @Test
+    void testParseMessageHistoryDuplicateNodes() {
+        String[] history = {
+                "route1[node1] (0 ms)",
+                "route1[node1] (5 ms)",
+                "route1[node2] (10 ms)"
+        };
+        HighlightInfo info = RouteDiagramHelper.parseMessageHistory(history, 
HighlightStyle.SUCCESS);
+
+        assertEquals(2, info.getNodeIds().size());
+        assertTrue(info.getNodeIds().contains("node1"));
+        assertTrue(info.getNodeIds().contains("node2"));
+    }
+
+    @Test
+    void testParseMessageHistoryRouteOrder() {
+        String[] history = {
+                "routeA[n1] (0 ms)",
+                "routeB[n2] (5 ms)",
+                "routeA[n3] (10 ms)",
+                "routeC[n4] (15 ms)"
+        };
+        HighlightInfo info = RouteDiagramHelper.parseMessageHistory(history, 
HighlightStyle.SUCCESS);
+
+        assertEquals(List.of("routeA", "routeB", "routeC"), 
info.getRouteOrder());
+    }
+
+    @Test
+    void testFilterAndOrderRoutesNullHighlight() {
+        List<RouteInfo> routes = List.of(routeWithNodeIds("r1", "n1", "n2"));
+        List<RouteInfo> result = 
RouteDiagramHelper.filterAndOrderRoutes(routes, null);
+
+        assertEquals(1, result.size());
+        assertEquals("r1", result.get(0).routeId);
+    }
+
+    @Test
+    void testFilterAndOrderRoutesFiltersNonHighlighted() {
+        RouteInfo r1 = routeWithNodeIds("route1", "n1", "n2");
+        RouteInfo r2 = routeWithNodeIds("route2", "n3", "n4");
+        RouteInfo r3 = routeWithNodeIds("route3", "n5", "n6");
+
+        HighlightInfo highlight = new HighlightInfo(
+                Set.of("n1", "n3"), List.of("route2", "route1"), 
HighlightStyle.SUCCESS);
+
+        List<RouteInfo> result = 
RouteDiagramHelper.filterAndOrderRoutes(List.of(r1, r2, r3), highlight);
+
+        assertEquals(2, result.size());
+        assertEquals("route2", result.get(0).routeId);
+        assertEquals("route1", result.get(1).routeId);
+    }
+
+    @Test
+    void testFilterAndOrderRoutesEmptyHighlight() {
+        RouteInfo r1 = routeWithNodeIds("route1", "n1");
+
+        HighlightInfo highlight = new HighlightInfo(Set.of(), List.of(), 
HighlightStyle.SUCCESS);
+
+        List<RouteInfo> result = 
RouteDiagramHelper.filterAndOrderRoutes(List.of(r1), highlight);
+        assertEquals(1, result.size());
+    }
+
+    @Test
+    void testNodeIdPropagatedToLayoutNode() {
+        RouteInfo route = new RouteInfo();
+        route.routeId = "route1";
+        route.nodes.add(nodeWithId("from", "timer:tick", 0, "from1"));
+        route.nodes.add(nodeWithId("to", "log:a", 1, "to1"));
+
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
+        LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
+
+        assertEquals("from1", lr.nodes.get(0).id);
+        assertEquals("to1", lr.nodes.get(1).id);
+    }
+
+    @Test
+    void testNodeIdNullSafe() {
+        RouteInfo route = new RouteInfo();
+        route.routeId = "route1";
+        route.nodes.add(node("from", "timer:tick", 0));
+
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
+        LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
+
+        assertNull(lr.nodes.get(0).id);
+    }
+
+    @Test
+    void testAsciiDiagramHighlightSuccess() {
+        RouteInfo route = new RouteInfo();
+        route.routeId = "route1";
+        route.nodes.add(nodeWithId("from", "timer:tick", 0, "from1"));
+        route.nodes.add(nodeWithId("to", "log:a", 1, "to1"));
+        route.nodes.add(nodeWithId("to", "log:b", 1, "to2"));
+
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
+        LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
+
+        Set<String> highlighted = Set.of("from1", "to1");
+        RouteDiagramAsciiRenderer renderer = new 
RouteDiagramAsciiRenderer(engine.getNodeWidth());
+        String ansi = renderer.renderDiagramAnsi(
+                List.of(lr), lr.maxY + RouteDiagramLayoutEngine.V_GAP,
+                highlighted, HighlightStyle.SUCCESS);
+
+        assertTrue(ansi.contains("\033[32m"), "Should contain green ANSI code 
for success highlight");
+        assertTrue(ansi.contains("\033[0m"), "Should contain ANSI reset code");
+        assertFalse(ansi.contains("\033[31m"), "Should not contain red ANSI 
code for success style");
+    }
+
+    @Test
+    void testAsciiDiagramHighlightFail() {
+        RouteInfo route = new RouteInfo();
+        route.routeId = "route1";
+        route.nodes.add(nodeWithId("from", "timer:tick", 0, "from1"));
+        route.nodes.add(nodeWithId("to", "log:a", 1, "to1"));
+
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
+        LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
+
+        Set<String> highlighted = Set.of("from1", "to1");
+        RouteDiagramAsciiRenderer renderer = new 
RouteDiagramAsciiRenderer(engine.getNodeWidth());
+        String ansi = renderer.renderDiagramAnsi(
+                List.of(lr), lr.maxY + RouteDiagramLayoutEngine.V_GAP,
+                highlighted, HighlightStyle.FAIL);
+
+        assertTrue(ansi.contains("\033[31m"), "Should contain red ANSI code 
for fail highlight");
+    }
+
+    @Test
+    void testAsciiDiagramHighlightOnlyTargetedNodes() {
+        RouteInfo route = new RouteInfo();
+        route.routeId = "route1";
+        route.nodes.add(nodeWithId("from", "timer:tick", 0, "from1"));
+        route.nodes.add(nodeWithId("to", "log:a", 1, "to1"));
+        route.nodes.add(nodeWithId("to", "log:b", 1, "to2"));
+
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
+        LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
+
+        // only highlight from1, not to1 or to2
+        Set<String> highlighted = Set.of("from1");
+        RouteDiagramAsciiRenderer renderer = new 
RouteDiagramAsciiRenderer(engine.getNodeWidth());
+        renderer.renderDiagram(
+                List.of(lr), lr.maxY + RouteDiagramLayoutEngine.V_GAP,
+                highlighted, HighlightStyle.SUCCESS);
+
+        long highlightCount = renderer.getCounterPositions().stream()
+                .filter(cp -> cp.type() == 
RouteDiagramAsciiRenderer.CounterType.HIGHLIGHT_SUCCESS)
+                .count();
+        assertTrue(highlightCount > 0, "Should have highlight positions for 
from1");
+    }
+
+    @Test
+    void testAsciiDiagramNoHighlightWithoutIds() {
+        RouteInfo route = new RouteInfo();
+        route.routeId = "route1";
+        route.nodes.add(nodeWithId("from", "timer:tick", 0, "from1"));
+        route.nodes.add(nodeWithId("to", "log:a", 1, "to1"));
+
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
+        LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
+
+        RouteDiagramAsciiRenderer renderer = new 
RouteDiagramAsciiRenderer(engine.getNodeWidth());
+        renderer.renderDiagram(
+                List.of(lr), lr.maxY + RouteDiagramLayoutEngine.V_GAP,
+                null, null);
+
+        long highlightCount = renderer.getCounterPositions().stream()
+                .filter(cp -> cp.type() == 
RouteDiagramAsciiRenderer.CounterType.HIGHLIGHT_SUCCESS
+                        || cp.type() == 
RouteDiagramAsciiRenderer.CounterType.HIGHLIGHT_FAIL)
+                .count();
+        assertEquals(0, highlightCount, "Should have no highlight positions 
when no IDs provided");
+    }
+
+    @Test
+    void testRenderDiagramWithHighlightSuccess() {
+        System.setProperty("java.awt.headless", "true");
+
+        RouteInfo route = new RouteInfo();
+        route.routeId = "route1";
+        route.nodes.add(nodeWithId("from", "timer:tick", 0, "from1"));
+        route.nodes.add(nodeWithId("to", "log:a", 1, "to1"));
+        route.nodes.add(nodeWithId("to", "log:b", 1, "to2"));
+
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
+        LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
+
+        RouteDiagramRenderer renderer = new RouteDiagramRenderer();
+        DiagramColors colors = DiagramColors.parse("dark");
+        Set<String> highlighted = Set.of("from1", "to1");
+        BufferedImage image = renderer.renderDiagram(
+                List.of(lr), lr.maxY + RouteDiagramLayoutEngine.V_GAP,
+                colors, highlighted, HighlightStyle.SUCCESS);
+
+        assertNotNull(image);
+        assertTrue(image.getWidth() > 0);
+        assertTrue(image.getHeight() > 0);
+    }
+
+    @Test
+    void testRenderDiagramWithHighlightFail() {
+        System.setProperty("java.awt.headless", "true");
+
+        RouteInfo route = new RouteInfo();
+        route.routeId = "route1";
+        route.nodes.add(nodeWithId("from", "timer:tick", 0, "from1"));
+        route.nodes.add(nodeWithId("to", "log:a", 1, "to1"));
+
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
+        LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
+
+        RouteDiagramRenderer renderer = new RouteDiagramRenderer();
+        DiagramColors colors = DiagramColors.parse("dark");
+        Set<String> highlighted = Set.of("from1", "to1");
+        BufferedImage image = renderer.renderDiagram(
+                List.of(lr), lr.maxY + RouteDiagramLayoutEngine.V_GAP,
+                colors, highlighted, HighlightStyle.FAIL);
+
+        assertNotNull(image);
+        assertTrue(image.getWidth() > 0);
+    }
+
+    @Test
+    void testRenderDiagramHighlightNullPassthrough() {
+        System.setProperty("java.awt.headless", "true");
+
+        RouteInfo route = new RouteInfo();
+        route.routeId = "route1";
+        route.nodes.add(nodeWithId("from", "timer:tick", 0, "from1"));
+
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
+        LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
+
+        RouteDiagramRenderer renderer = new RouteDiagramRenderer();
+        DiagramColors colors = DiagramColors.parse("dark");
+        BufferedImage image = renderer.renderDiagram(
+                List.of(lr), lr.maxY + RouteDiagramLayoutEngine.V_GAP,
+                colors, null, null);
+
+        assertNotNull(image);
+    }
+
+    @Test
+    void testUnicodeDiagramHighlight() {
+        RouteInfo route = new RouteInfo();
+        route.routeId = "route1";
+        route.nodes.add(nodeWithId("from", "timer:tick", 0, "from1"));
+        route.nodes.add(nodeWithId("to", "log:a", 1, "to1"));
+
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine();
+        LayoutRoute lr = engine.layoutRoute(route, 
RouteDiagramLayoutEngine.PADDING);
+
+        Set<String> highlighted = Set.of("from1");
+        RouteDiagramAsciiRenderer renderer = new 
RouteDiagramAsciiRenderer(engine.getNodeWidth(), true);
+        String ansi = renderer.renderDiagramAnsi(
+                List.of(lr), lr.maxY + RouteDiagramLayoutEngine.V_GAP,
+                highlighted, HighlightStyle.SUCCESS);
+
+        assertTrue(ansi.contains("\033[32m"), "Unicode mode should also apply 
ANSI highlight colors");
+        assertTrue(ansi.contains("┌"), "Should still use Unicode box-drawing 
characters");
+    }
+
     private static NodeInfo node(String type, String code, int level) {
         return node(type, code, level, null);
     }
@@ -1151,4 +1462,21 @@ class RouteDiagramTest {
         n.stat = stat;
         return n;
     }
+
+    private static NodeInfo nodeWithId(String type, String code, int level, 
String id) {
+        NodeInfo n = node(type, code, level);
+        n.id = id;
+        return n;
+    }
+
+    private static RouteInfo routeWithNodeIds(String routeId, String... 
nodeIds) {
+        RouteInfo route = new RouteInfo();
+        route.routeId = routeId;
+        for (int i = 0; i < nodeIds.length; i++) {
+            NodeInfo n = node(i == 0 ? "from" : "to", "endpoint" + i, i == 0 ? 
0 : 1);
+            n.id = nodeIds[i];
+            route.nodes.add(n);
+        }
+        return route;
+    }
 }

Reply via email to