#![cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use crate::models::dag::{DependencyGraph, Edge, EdgeType, NodeInfo, NodeType};
use crate::services::mermaid_generator::{MermaidGenerator, MermaidOptions};
use rustc_hash::FxHashMap;
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod validation_tests {
use super::*;
use crate::models::dag::{DependencyGraph, Edge, EdgeType, NodeInfo, NodeType};
#[test]
fn test_angle_brackets_and_pipes_compatibility() {
let mut graph = DependencyGraph::new();
graph.add_node(NodeInfo {
id: "cache.rs::Cache<K,V>".to_string(),
label: "Cache<K,V>".to_string(),
node_type: NodeType::Interface,
file_path: "cache.rs".to_string(),
line_number: 1,
complexity: 6,
metadata: FxHashMap::default(),
});
let generator = MermaidGenerator::new(MermaidOptions {
show_complexity: true,
..Default::default()
});
let output = generator.generate(&graph);
assert!(!output.contains("<")); assert!(!output.contains(">")); assert!(!output.contains("&#"));
assert!(output.contains("cache_rs_Cache_K_V_")); }
#[test]
fn test_edge_arrow_syntax() {
let generator = MermaidGenerator::default();
let edge_types = vec![
EdgeType::Calls,
EdgeType::Imports,
EdgeType::Inherits,
EdgeType::Implements,
EdgeType::Uses,
];
for edge_type in edge_types {
let arrow = generator.get_edge_arrow(&edge_type);
assert!(
arrow.contains("-"),
"Arrow '{arrow}' for {edge_type:?} must contain dash"
);
match &edge_type {
EdgeType::Inherits | EdgeType::Implements => {
assert!(
arrow.contains("|"),
"Labeled edge '{arrow}' should contain pipe"
);
}
_ => {
assert!(
!arrow.contains(" "),
"Arrow '{arrow}' should not contain spaces"
);
assert!(
!arrow.contains("|"),
"Arrow '{arrow}' should not contain pipes"
);
}
}
}
}
#[test]
fn test_realistic_dependency_graph() {
let mut graph = DependencyGraph::new();
let nodes = vec![
("main.rs::main", "main", NodeType::Function, 3),
("lib.rs::Config", "Config", NodeType::Class, 5),
("error.rs::AppError", "AppError", NodeType::Class, 7),
("traits.rs::Processor", "Processor", NodeType::Trait, 4),
("utils.rs", "utils", NodeType::Module, 2),
("api.rs::Handler<T>", "Handler<T>", NodeType::Interface, 8),
];
for (id, label, node_type, complexity) in nodes {
graph.add_node(NodeInfo {
id: id.to_string(),
label: label.to_string(),
node_type,
file_path: format!("{}.rs", id.split("::").next().unwrap().replace(".rs", "")),
line_number: 1,
complexity,
metadata: FxHashMap::default(),
});
}
let edges = vec![
("main.rs::main", "lib.rs::Config", EdgeType::Uses),
("main.rs::main", "api.rs::Handler<T>", EdgeType::Calls),
(
"api.rs::Handler<T>",
"traits.rs::Processor",
EdgeType::Implements,
),
("lib.rs::Config", "error.rs::AppError", EdgeType::Uses),
];
for (from, to, edge_type) in edges {
graph.add_edge(Edge {
from: from.to_string(),
to: to.to_string(),
edge_type,
weight: 1,
});
}
let generator = MermaidGenerator::new(MermaidOptions {
show_complexity: true,
..Default::default()
});
let output = generator.generate(&graph);
assert!(output.starts_with("graph TD\n"));
assert!(output.contains("style "));
assert!(!output.contains("<")); assert!(!output.contains("&")); assert!(
!output.contains("|")
|| output.contains("implements")
|| output.contains("inherits")
);
assert!(output.contains("main_rs_main"));
assert!(output.contains("lib_rs_Config"));
assert!(output.contains("api_rs_Handler_T_"));
assert!(output.contains("#FFD700")); assert!(output.contains("#FFA500")); }
}
#[test]
fn test_string_write_never_fails() {
let generator = MermaidGenerator::new(MermaidOptions {
show_complexity: true,
..Default::default()
});
let mut nodes = FxHashMap::default();
nodes.insert(
"test::module::function".to_string(),
NodeInfo {
id: "test::module::function".to_string(),
label: "test::module::function".to_string(),
node_type: NodeType::Function,
file_path: "test.rs".to_string(),
line_number: 1,
complexity: 10,
metadata: FxHashMap::default(),
},
);
let graph = DependencyGraph {
nodes,
edges: vec![],
};
let output = generator.generate(&graph);
assert!(output.contains("graph TD"));
assert!(output.contains("test_module_function"));
assert!(output.contains("style ")); }
#[test]
fn test_sanitize_id_edge_cases() {
let generator = MermaidGenerator::default();
assert_eq!(generator.sanitize_id(""), "_empty");
assert_eq!(generator.sanitize_id("123module"), "_123module");
assert_eq!(generator.sanitize_id("0start"), "_0start");
assert_eq!(generator.sanitize_id("validModule"), "validModule");
assert_eq!(generator.sanitize_id("_underscore"), "_underscore");
assert_eq!(generator.sanitize_id("my::module"), "my_module");
assert_eq!(generator.sanitize_id("path/to/file"), "path_to_file");
}
#[test]
fn test_complex_module_names() {
let generator = MermaidGenerator::new(MermaidOptions {
show_complexity: true,
..Default::default()
});
let mut nodes = FxHashMap::default();
nodes.insert(
"std::collections::HashMap".to_string(),
NodeInfo {
id: "std::collections::HashMap".to_string(),
label: "std::collections::HashMap".to_string(),
node_type: NodeType::Class,
file_path: "std.rs".to_string(),
line_number: 1,
complexity: 15,
metadata: FxHashMap::default(),
},
);
nodes.insert(
"123_numeric_start".to_string(),
NodeInfo {
id: "123_numeric_start".to_string(),
label: "123_numeric_start".to_string(),
node_type: NodeType::Function,
file_path: "test.rs".to_string(),
line_number: 5,
complexity: 5,
metadata: FxHashMap::default(),
},
);
nodes.insert(
"".to_string(),
NodeInfo {
id: "".to_string(),
label: "".to_string(),
node_type: NodeType::Module,
file_path: "empty.rs".to_string(),
line_number: 1,
complexity: 1,
metadata: FxHashMap::default(),
},
);
let graph = DependencyGraph {
nodes,
edges: vec![
Edge {
from: "std::collections::HashMap".to_string(),
to: "123_numeric_start".to_string(),
edge_type: EdgeType::Calls,
weight: 1,
},
Edge {
from: "123_numeric_start".to_string(),
to: "".to_string(),
edge_type: EdgeType::Uses,
weight: 1,
},
],
};
let output = generator.generate(&graph);
assert!(output.contains("std_collections_HashMap")); assert!(output.contains("_123_numeric_start")); assert!(output.contains("_empty"));
assert!(output.contains("style "));
assert!(output.contains("-->") || output.contains("---"));
}
#[test]
fn test_edge_generation() {
let generator = MermaidGenerator::default();
let mut nodes = FxHashMap::default();
nodes.insert(
"node1".to_string(),
NodeInfo {
id: "node1".to_string(),
label: "Node 1".to_string(),
node_type: NodeType::Function,
file_path: "test.rs".to_string(),
line_number: 1,
complexity: 5,
metadata: FxHashMap::default(),
},
);
nodes.insert(
"node2".to_string(),
NodeInfo {
id: "node2".to_string(),
label: "Node 2".to_string(),
node_type: NodeType::Function,
file_path: "test.rs".to_string(),
line_number: 10,
complexity: 5,
metadata: FxHashMap::default(),
},
);
let graph = DependencyGraph {
nodes,
edges: vec![
Edge {
from: "node1".to_string(),
to: "node2".to_string(),
edge_type: EdgeType::Calls,
weight: 1,
},
Edge {
from: "node2".to_string(),
to: "node1".to_string(),
edge_type: EdgeType::Imports,
weight: 1,
},
],
};
let output = generator.generate(&graph);
assert!(output.contains("-->")); assert!(output.contains("-.->") || output.contains("Imports")); }
#[test]
fn test_styling_generation() {
let generator = MermaidGenerator::new(MermaidOptions {
show_complexity: true,
..Default::default()
});
let mut nodes = FxHashMap::default();
nodes.insert(
"low".to_string(),
NodeInfo {
id: "low".to_string(),
label: "Low Complexity".to_string(),
node_type: NodeType::Function,
file_path: "test.rs".to_string(),
line_number: 1,
complexity: 5,
metadata: FxHashMap::default(),
},
);
nodes.insert(
"high".to_string(),
NodeInfo {
id: "high".to_string(),
label: "High Complexity".to_string(),
node_type: NodeType::Function,
file_path: "test.rs".to_string(),
line_number: 20,
complexity: 25,
metadata: FxHashMap::default(),
},
);
let graph = DependencyGraph {
nodes,
edges: vec![],
};
let output = generator.generate(&graph);
assert!(output.contains("style low"));
assert!(output.contains("style high"));
assert!(output.contains("#90EE90") || output.contains("#FFD700")); assert!(output.contains("#FF6347") || output.contains("#FFA500")); }
}