use pmat::models::dag::{DependencyGraph, NodeInfo, NodeType};
use pmat::services::mermaid_generator::{MermaidGenerator, MermaidOptions};
use rustc_hash::FxHashMap;
#[test]
fn test_regression_empty_nodes_bug() {
let mut graph = DependencyGraph::new();
let test_cases = vec![
("fn_process", "fn|process", "Function with pipe"),
("struct_T", "struct <T>", "Generic struct"),
("impl_Display", "impl Display for &'a str", "Complex impl"),
("async_fn", "async fn handle_request()", "Async function"),
(
"mod_tests",
"mod tests { #[test] }",
"Module with attributes",
),
(
"trait_Iterator",
"trait Iterator<Item=T>",
"Associated type",
),
("use_io", "use std::io::{Read, Write}", "Multiple imports"),
];
for (id, label, _description) in &test_cases {
graph.add_node(NodeInfo {
id: (*id).to_string(),
label: (*label).to_string(),
node_type: NodeType::Function,
file_path: String::new(),
line_number: 0,
complexity: 5,
metadata: FxHashMap::default(),
});
}
let generator = MermaidGenerator::new(MermaidOptions::default());
let output = generator.generate(&graph);
println!("Generated Mermaid diagram:\n{output}");
for (_id, label, description) in &test_cases {
let escaped = generator.escape_mermaid_label(label);
assert!(
output.contains(&escaped),
"Label '{label}' ({description}) not found in output. Escaped label: '{escaped}'"
);
}
for (id, _, _) in &test_cases {
let sanitized_id = generator.sanitize_id(id);
let bare_id_pattern = format!(" {sanitized_id}\n");
assert!(
!output.contains(&bare_id_pattern),
"Found bare ID '{sanitized_id}' without label in output"
);
}
assert!(output.contains('['), "No node labels with brackets found");
assert!(output.contains("graph TD"), "Missing graph declaration");
}
#[test]
fn test_mermaid_label_escaping() {
let generator = MermaidGenerator::new(MermaidOptions::default());
let test_cases = vec![
("simple", "simple"),
("with|pipe", "with - pipe"),
("with\"quote", "with'quote"),
("with<angle>", "with(angle)"),
("with[bracket]", "with(bracket)"),
("with{brace}", "with(brace)"),
("with\nnewline", "with newline"),
("with&ersand", "with and ampersand"),
];
for (input, expected) in test_cases {
let escaped = generator.escape_mermaid_label(input);
assert_eq!(
escaped, expected,
"Escaping '{input}' failed. Expected: '{expected}', Got: '{escaped}'"
);
}
}
#[test]
fn test_node_types_have_labels() {
let mut graph = DependencyGraph::new();
let node_types = vec![
("module", "TestModule", NodeType::Module),
("function", "test_function", NodeType::Function),
("class", "TestClass", NodeType::Class),
("trait", "TestTrait", NodeType::Trait),
("interface", "TestInterface", NodeType::Interface),
];
for (id, label, node_type) in &node_types {
graph.add_node(NodeInfo {
id: (*id).to_string(),
label: (*label).to_string(),
node_type: node_type.clone(),
file_path: String::new(),
line_number: 0,
complexity: 1,
metadata: FxHashMap::default(),
});
}
let generator = MermaidGenerator::new(MermaidOptions::default());
let output = generator.generate(&graph);
for (_id, label, _node_type) in &node_types {
assert!(
output.contains(label),
"Node label '{label}' not found in output"
);
}
}
#[test]
fn test_complexity_styled_diagram_has_labels() {
let mut graph = DependencyGraph::new();
graph.add_node(NodeInfo {
id: "complex_fn".to_string(),
label: "process_data".to_string(),
node_type: NodeType::Function,
file_path: String::new(),
line_number: 0,
complexity: 15,
metadata: FxHashMap::default(),
});
graph.add_node(NodeInfo {
id: "simple_fn".to_string(),
label: "get_value".to_string(),
node_type: NodeType::Function,
file_path: String::new(),
line_number: 0,
complexity: 2,
metadata: FxHashMap::default(),
});
let generator = MermaidGenerator::new(MermaidOptions {
show_complexity: true,
filter_external: false,
max_depth: None,
group_by_module: false,
});
let output = generator.generate(&graph);
assert!(
output.contains("process_data"),
"Complex function label missing"
);
assert!(
output.contains("get_value"),
"Simple function label missing"
);
assert!(output.contains("style"), "No style declarations found");
assert!(output.contains("fill:"), "No fill colors found");
}
#[test]
fn test_empty_graph_doesnt_crash() {
let graph = DependencyGraph::new();
let generator = MermaidGenerator::new(MermaidOptions::default());
let output = generator.generate(&graph);
assert!(
output.contains("graph TD"),
"Empty graph should still have declaration"
);
assert!(!output.contains('['), "Empty graph should have no nodes");
}
#[test]
fn test_special_characters_in_node_ids() {
let mut graph = DependencyGraph::new();
let test_ids = vec![
("src/lib.rs::main", "main function"),
("module::sub-module", "nested module"),
("file.name.with.dots", "dotted name"),
("123_starts_with_number", "numeric start"),
("", "empty id"),
];
for (id, label) in &test_ids {
graph.add_node(NodeInfo {
id: (*id).to_string(),
label: (*label).to_string(),
node_type: NodeType::Function,
file_path: String::new(),
line_number: 0,
complexity: 1,
metadata: FxHashMap::default(),
});
}
let generator = MermaidGenerator::new(MermaidOptions::default());
let output = generator.generate(&graph);
for (_id, label) in &test_ids {
if !label.is_empty() {
assert!(
output.contains(label),
"Label '{label}' not found in output"
);
}
}
assert!(!output.contains("::"), "Unescaped :: found in output");
assert!(!output.contains("src/"), "Unescaped path separator found");
}