@@ -8,6 +8,7 @@ use std::path::Path;
88
99use serde:: Deserialize ;
1010
11+ use super :: types:: AcyclicPolicy ;
1112use super :: { DiGraph , LayoutConfig , NodeId , layout} ;
1213
1314#[ test]
@@ -115,6 +116,14 @@ fn build_digraph_from_input(input: &InputGraph) -> DiGraph<(f64, f64)> {
115116 graph
116117}
117118
119+ fn dagre_node < ' a > ( layout : & ' a DagreLayout , id : & str ) -> & ' a DagreNode {
120+ layout
121+ . nodes
122+ . iter ( )
123+ . find ( |node| node. id == id)
124+ . unwrap_or_else ( || panic ! ( "missing dagre node {id}" ) )
125+ }
126+
118127/// Border node info parsed from debug dump files.
119128#[ derive( Debug , Clone ) ]
120129struct BorderNodeInfo {
@@ -229,6 +238,80 @@ fn assert_points_close(actual: &[(f64, f64)], expected: &[[f64; 2]], tolerance:
229238// Parity Tests
230239// =============================================================================
231240
241+ mod compound_backward_disconnected {
242+ use super :: * ;
243+
244+ const INPUT_PATH : & str =
245+ "tests/parity-fixtures/compound_backward_disconnected/mmdflux-dagre-input.json" ;
246+ const EXPECTED_PATH : & str =
247+ "tests/parity-fixtures/compound_backward_disconnected/dagre-layout.json" ;
248+
249+ #[ test]
250+ fn compound_backward_disconnected_input_records_mermaid_order ( ) {
251+ let input: InputGraph = load_json ( INPUT_PATH ) ;
252+ let ids: Vec < & str > = input. nodes . iter ( ) . map ( |node| node. id . as_str ( ) ) . collect ( ) ;
253+
254+ assert_eq ! (
255+ ids,
256+ vec![ "C" , "B" , "A" , "a1" , "a2" , "b1" , "b2" , "c1" , "c2" ] ,
257+ "raw Dagre fixture should preserve Mermaid FlowDB node order"
258+ ) ;
259+ }
260+
261+ #[ test]
262+ fn compound_backward_disconnected_raw_dagre_fixture_is_tall_top_right ( ) {
263+ let expected: DagreLayout = load_json ( EXPECTED_PATH ) ;
264+ let top = dagre_node ( & expected, "A" ) ;
265+ let middle = dagre_node ( & expected, "B" ) ;
266+ let bottom = dagre_node ( & expected, "C" ) ;
267+ let sibling_max = middle. height . max ( bottom. height ) ;
268+
269+ assert ! ( top. is_compound && middle. is_compound && bottom. is_compound) ;
270+ assert ! (
271+ top. x > middle. x && top. x > bottom. x,
272+ "raw Dagre should place A/Top to the right; A={top:?} B={middle:?} C={bottom:?}"
273+ ) ;
274+ assert ! (
275+ top. height > sibling_max * 1.8 ,
276+ "raw Dagre should stretch A/Top vertically; A={top:?} B={middle:?} C={bottom:?}"
277+ ) ;
278+ assert ! (
279+ middle. y + middle. height <= bottom. y,
280+ "raw Dagre should stack B/Middle above C/Bottom; B={middle:?} C={bottom:?}"
281+ ) ;
282+
283+ let edge2 = expected
284+ . edges
285+ . iter ( )
286+ . find ( |edge| edge. index == 2 )
287+ . expect ( "raw Dagre fixture should include edge 2" ) ;
288+ assert_eq ! ( edge2. _from, "c2" ) ;
289+ assert_eq ! ( edge2. _to, "a2" ) ;
290+ }
291+
292+ #[ test]
293+ fn compound_backward_disconnected_dfs_only_keeps_edge_2_forward ( ) {
294+ let input: InputGraph = load_json ( INPUT_PATH ) ;
295+ let graph = build_digraph_from_input ( & input) ;
296+ let config = LayoutConfig {
297+ node_sep : 50.0 ,
298+ edge_sep : 20.0 ,
299+ rank_sep : 75.0 ,
300+ margin : 8.0 ,
301+ acyclic_policy : AcyclicPolicy :: DfsOnly ,
302+ ..Default :: default ( )
303+ } ;
304+
305+ let result = layout ( & graph, & config, |_, dims| * dims) ;
306+
307+ assert ! (
308+ !result. reversed_edges. contains( & 2 ) ,
309+ "DFS-only strict parity should not reverse c2 -> a2; got {:?}" ,
310+ result. reversed_edges
311+ ) ;
312+ }
313+ }
314+
232315mod subgraph_bounds {
233316 use super :: * ;
234317
0 commit comments