use graphs_tui::{
detect_format, render_d2_to_tui, render_diagram, render_mermaid_to_tui, render_pie_chart,
render_sequence_diagram, render_state_diagram, DiagramFormat, MermaidError, RenderOptions,
};
#[test]
fn test_simple_lr_flowchart() {
let input = "flowchart LR\nA[Start] --> B[End]";
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Start"));
assert!(result.output.contains("End"));
assert!(result.output.contains("▶"));
}
#[test]
fn test_simple_tb_flowchart() {
let input = "flowchart TB\nA[Start] --> B[End]";
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Start"));
assert!(result.output.contains("End"));
assert!(result.output.contains("▼"));
}
#[test]
fn test_labels_correctly() {
let input = "flowchart LR\nA[Node A] --> B[Node B]";
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Node A"));
assert!(result.output.contains("Node B"));
}
#[test]
fn test_ascii_mode() {
let input = "flowchart LR\nA --> B";
let result = render_mermaid_to_tui(
input,
RenderOptions {
ascii: true,
max_width: None,
},
)
.unwrap();
assert!(result.output.contains("+---+"));
assert!(result.output.contains(">"));
assert!(!result.output.contains("┌"));
}
#[test]
fn test_unsupported_diagram_type() {
let input = "sequenceDiagram\nA->B: hi";
let result = render_mermaid_to_tui(input, RenderOptions::default());
assert!(matches!(
result,
Err(MermaidError::ParseError { line: 1, .. })
));
}
#[test]
fn test_chained_edges() {
let input = "flowchart LR\nA --> B --> C --> D";
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("A"));
assert!(result.output.contains("B"));
assert!(result.output.contains("C"));
assert!(result.output.contains("D"));
}
#[test]
fn test_labels_with_spaces_and_special_chars() {
let input = "flowchart LR\nA[Start Here] --> B[Wait... what?]\nB --> C[Done!]";
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Start Here"));
assert!(result.output.contains("Wait... what?"));
assert!(result.output.contains("Done!"));
}
#[test]
fn test_rl_direction() {
let input = "flowchart RL\nA --> B";
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("◀"));
}
#[test]
fn test_bt_direction() {
let input = "flowchart BT\nA --> B";
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("▲"));
}
#[test]
fn test_empty_input() {
let result = render_mermaid_to_tui("", RenderOptions::default());
assert!(matches!(result, Err(MermaidError::EmptyInput)));
}
#[test]
fn test_comments_ignored() {
let input = "flowchart LR\n%% this is a comment\nA --> B";
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("A"));
assert!(result.output.contains("B"));
assert!(!result.output.contains("comment"));
}
#[test]
fn test_node_label_update() {
let input = "flowchart LR\nA\nB[Label B]\nA --> B\nA[Label A]";
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Label A"));
assert!(result.output.contains("Label B"));
}
#[test]
fn test_graph_td_supported() {
let input = "graph TD\nA --> B";
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("A"));
assert!(result.output.contains("B"));
assert!(result.output.contains("▼")); }
#[test]
fn test_web_architecture_simplified() {
let input = r#"flowchart TB
User[User] --> CDN[CDN]
CDN --> Browser[Browser]
Browser --> API[API Gateway]
API --> App[App Server]
App --> Auth[Auth Service]
App --> Cache[Cache]
App --> DB[Database]
App --> Queue[Message Queue]
Queue --> Worker[Worker]
Worker --> DB
App --> Payment[Payment]
Worker --> Email[Email]"#;
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("User"));
assert!(result.output.contains("CDN"));
assert!(result.output.contains("Browser"));
assert!(result.output.contains("API Gateway"));
assert!(result.output.contains("App Server"));
assert!(result.output.contains("Database"));
assert!(result.output.contains("Cache"));
assert!(result.output.contains("Worker"));
println!("Web Architecture (simplified):\n{}", result.output);
}
#[test]
fn test_edge_labels() {
let input = r#"flowchart LR
A[Client] -->|HTTP| B[Server]"#;
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Client"));
assert!(result.output.contains("Server"));
assert!(result.output.contains("HTTP"));
println!("Edge label test:\n{}", result.output);
}
#[test]
fn test_web_architecture_with_edge_labels() {
let input = r#"flowchart TB
User[User] -->|HTTPS| CDN[CDN]
CDN -->|static| Browser[Browser]
Browser -->|API call| API[API Gateway]
API -->|route| App[App Server]
App -->|validate| Auth[Auth Service]
App -->|read/write| Cache[Cache]
App -->|persist| DB[Database]
App -->|enqueue| Queue[Message Queue]
Queue -->|process| Worker[Worker]
Worker -->|update| DB
App -->|charge| Payment[Payment]
Worker -->|notify| Email[Email]"#;
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("User"));
assert!(result.output.contains("CDN"));
assert!(result.output.contains("Browser"));
assert!(result.output.contains("API Gateway"));
assert!(result.output.contains("App Server"));
assert!(result.output.contains("Database"));
assert!(
result.output.contains("HTTPS")
|| result.output.contains("static")
|| result.output.contains("route")
);
println!("Web Architecture with edge labels:\n{}", result.output);
}
#[test]
fn test_node_shapes() {
let input = r#"flowchart LR
A[Rectangle] --> B(Rounded)
B --> C((Circle))
C --> D{Diamond}
D --> E[(Database)]
E --> F([Stadium])"#;
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Rectangle"));
assert!(result.output.contains("Rounded"));
assert!(result.output.contains("Circle"));
assert!(result.output.contains("Diamond"));
assert!(result.output.contains("Database"));
assert!(result.output.contains("Stadium"));
assert!(result.output.contains("╭")); assert!(result.output.contains("<"));
println!("Node shapes:\n{}", result.output);
}
#[test]
fn test_subgraph_parsing() {
let input = r#"flowchart TB
subgraph Backend [Backend Services]
API[API Server]
DB[(Database)]
end
API --> DB"#;
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("API Server"));
assert!(result.output.contains("Database"));
println!("Subgraph:\n{}", result.output);
}
#[test]
fn test_hexagon_shape() {
let input = "flowchart LR\nA{{Prepare}} --> B{{Execute}}";
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Prepare"));
assert!(result.output.contains("Execute"));
assert!(result.output.contains("<")); println!("Hexagon:\n{}", result.output);
}
#[test]
fn test_edge_styles() {
let input1 = "flowchart LR\nA --> B";
let r1 = render_mermaid_to_tui(input1, RenderOptions::default()).unwrap();
assert!(r1.output.contains("▶"));
println!("Solid arrow:\n{}", r1.output);
let input2 = "flowchart LR\nA --- B";
let r2 = render_mermaid_to_tui(input2, RenderOptions::default()).unwrap();
assert!(r2.output.contains("─")); println!("Solid line:\n{}", r2.output);
let input3 = "flowchart LR\nA -.-> B";
let r3 = render_mermaid_to_tui(input3, RenderOptions::default()).unwrap();
assert!(r3.output.contains("A"));
assert!(r3.output.contains("B"));
assert!(r3.output.contains("·")); println!("Dotted arrow:\n{}", r3.output);
let input4 = "flowchart LR\nA ==> B";
let r4 = render_mermaid_to_tui(input4, RenderOptions::default()).unwrap();
assert!(r4.output.contains("A"));
assert!(r4.output.contains("B"));
assert!(r4.output.contains("═")); println!("Thick arrow:\n{}", r4.output);
}
#[test]
fn test_parallelogram_trapezoid_shapes() {
let input = "flowchart LR\nA[/Input/] --> B[/Process\\]";
let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Input"));
assert!(result.output.contains("Process"));
println!("Parallelogram/Trapezoid:\n{}", result.output);
}
#[test]
fn test_d2_simple() {
let input = "A -> B";
let result = render_d2_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("A"));
assert!(result.output.contains("B"));
println!("D2 simple:\n{}", result.output);
}
#[test]
fn test_d2_labels() {
let input = r#"
server: "Web Server"
db: Database
server -> db
"#;
let result = render_d2_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Web Server"));
assert!(result.output.contains("Database"));
println!("D2 labels:\n{}", result.output);
}
#[test]
fn test_d2_edge_labels() {
let input = r#"client -> server: "HTTP request"
server -> db: "SQL query""#;
let result = render_d2_to_tui(input, RenderOptions::default()).unwrap();
println!("D2 edge labels:\n{}", result.output);
assert!(result.output.contains("client"));
assert!(result.output.contains("server"));
assert!(result.output.contains("db"));
}
#[test]
fn test_d2_shapes() {
let input = r#"
db: Database
db.shape: cylinder
circle_node: Circle
circle_node.shape: circle
db -> circle_node
"#;
let result = render_d2_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Database"));
assert!(result.output.contains("Circle"));
println!("D2 shapes:\n{}", result.output);
}
#[test]
fn test_d2_containers() {
let input = r#"
backend {
api: "API Server"
db: Database
}
frontend {
web: "Web App"
}
web -> api
api -> db
"#;
let result = render_d2_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("API Server"));
assert!(result.output.contains("Database"));
assert!(result.output.contains("Web App"));
println!("D2 containers:\n{}", result.output);
}
#[test]
fn test_d2_backward_arrow() {
let input = "A <- B";
let result = render_d2_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("A"));
assert!(result.output.contains("B"));
println!("D2 backward arrow:\n{}", result.output);
}
#[test]
fn test_d2_line() {
let input = "A -- B";
let result = render_d2_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("A"));
assert!(result.output.contains("B"));
println!("D2 line:\n{}", result.output);
}
#[test]
fn test_format_detection_mermaid() {
assert_eq!(
detect_format("flowchart LR\nA --> B"),
DiagramFormat::Mermaid
);
assert_eq!(detect_format("graph TD\nA --> B"), DiagramFormat::Mermaid);
assert_eq!(detect_format("A --> B --> C"), DiagramFormat::Mermaid);
}
#[test]
fn test_format_detection_d2() {
assert_eq!(detect_format("A -> B"), DiagramFormat::D2);
assert_eq!(
detect_format("server: Web Server\nserver -> db"),
DiagramFormat::D2
);
}
#[test]
fn test_render_diagram_auto() {
let mermaid = "flowchart LR\nA[Start] --> B[End]";
let result1 = render_diagram(mermaid, RenderOptions::default()).unwrap();
assert!(result1.output.contains("Start"));
assert!(result1.output.contains("End"));
let d2 = r#"start: Start
end: End
start -> end"#;
let result2 = render_diagram(d2, RenderOptions::default()).unwrap();
assert!(result2.output.contains("Start"));
assert!(result2.output.contains("End"));
}
#[test]
fn test_d2_web_architecture() {
let input = r#"
user: User
cdn: CDN
browser: Browser
api: "API Gateway"
app: "App Server"
db: Database
cache: Cache
queue: "Message Queue"
worker: Worker
user -> cdn: HTTPS
cdn -> browser: static
browser -> api: "API call"
api -> app: route
app -> db: persist
app -> cache: read
app -> queue: enqueue
queue -> worker: process
worker -> db: update
"#;
let result = render_d2_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("User"));
assert!(result.output.contains("CDN"));
assert!(result.output.contains("API Gateway"));
assert!(result.output.contains("Database"));
println!("D2 Web Architecture:\n{}", result.output);
}
#[test]
fn test_state_diagram_simple() {
let input = r#"stateDiagram-v2
[*] --> Idle
Idle --> Running
Running --> [*]
"#;
let result = render_state_diagram(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Idle"));
assert!(result.output.contains("Running"));
println!("State diagram simple:\n{}", result.output);
}
#[test]
fn test_state_diagram_descriptions() {
let input = r#"stateDiagram-v2
state "Waiting for input" as Waiting
state "Processing data" as Processing
Waiting --> Processing: submit
Processing --> Waiting: reset
"#;
let result = render_state_diagram(input, RenderOptions::default()).unwrap();
println!("State diagram descriptions:\n{}", result.output);
assert!(result.output.contains("Waiting") || result.output.contains("Wait"));
assert!(result.output.contains("Process"));
assert!(result.output.contains("reset")); }
#[test]
fn test_state_diagram_composite() {
let input = r#"stateDiagram-v2
[*] --> Active
state Active {
[*] --> Running
Running --> Paused
Paused --> Running
}
Active --> [*]
"#;
let result = render_state_diagram(input, RenderOptions::default()).unwrap();
println!("State diagram composite:\n{}", result.output);
assert!(result.output.contains("Active"));
assert!(result.output.contains("Running"));
}
#[test]
fn test_state_diagram_v1() {
let input = r#"stateDiagram
s1 --> s2
s2 --> s3
"#;
let result = render_state_diagram(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("s1"));
assert!(result.output.contains("s2"));
assert!(result.output.contains("s3"));
println!("State diagram v1:\n{}", result.output);
}
#[test]
fn test_pie_chart_simple() {
let input = r#"pie
title Browser Market Share
"Chrome" : 65
"Firefox" : 15
"Safari" : 12
"Edge" : 8
"#;
let result = render_pie_chart(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Chrome"));
assert!(result.output.contains("Firefox"));
assert!(result.output.contains("65"));
println!("Pie chart:\n{}", result.output);
}
#[test]
fn test_pie_chart_no_title() {
let input = r#"pie
"Yes" : 70
"No" : 30
"#;
let result = render_pie_chart(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Yes"));
assert!(result.output.contains("No"));
assert!(result.output.contains("70"));
assert!(result.output.contains("30"));
println!("Pie chart no title:\n{}", result.output);
}
#[test]
fn test_pie_chart_show_data() {
let input = r#"pie showData
title Project Status
"Completed" : 45
"In Progress" : 35
"Not Started" : 20
"#;
let result = render_pie_chart(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Completed"));
assert!(result.output.contains("45"));
println!("Pie chart showData:\n{}", result.output);
}
#[test]
fn test_sequence_diagram_simple() {
let input = r#"sequenceDiagram
Alice->>Bob: Hello Bob!
Bob-->>Alice: Hi Alice!
"#;
let result = render_sequence_diagram(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Alice"));
assert!(result.output.contains("Bob"));
assert!(result.output.contains("Hello Bob!"));
assert!(result.output.contains("Hi Alice!"));
println!("Sequence diagram simple:\n{}", result.output);
}
#[test]
fn test_sequence_diagram_participants() {
let input = r#"sequenceDiagram
participant A as Alice
participant B as Bob
A->>B: Message
"#;
let result = render_sequence_diagram(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Alice"));
assert!(result.output.contains("Bob"));
println!("Sequence diagram participants:\n{}", result.output);
}
#[test]
fn test_sequence_diagram_detection() {
let input = "sequenceDiagram\n A->>B: Hi";
assert_eq!(detect_format(input), DiagramFormat::SequenceDiagram);
}
#[test]
fn test_sequence_diagram_auto() {
let input = r#"sequenceDiagram
Client->>Server: Request
Server-->>Client: Response
"#;
let result = render_diagram(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Client"));
assert!(result.output.contains("Server"));
println!("Sequence auto-detect:\n{}", result.output);
}
#[test]
fn test_d2_sql_table_shape() {
let input = r#"
users: Users
users.shape: sql_table
orders: Orders
orders.shape: sql_table
users -> orders
"#;
let result = render_d2_to_tui(input, RenderOptions::default()).unwrap();
assert!(result.output.contains("Users"));
assert!(result.output.contains("Orders"));
assert!(result.output.contains('═') || result.output.contains('╔'));
println!("D2 sql_table output:\n{}", result.output);
}
#[test]
fn test_max_width_constraint() {
let input = "flowchart LR\nA[Very Long Label Here] --> B[Another Long Label]";
let result = render_mermaid_to_tui(
input,
RenderOptions {
ascii: false,
max_width: Some(30),
},
)
.unwrap();
for line in result.output.lines() {
assert!(
line.chars().count() <= 30,
"Line too long: {} chars",
line.chars().count()
);
}
assert!(result.output.contains('…'));
println!("Max width constrained output:\n{}", result.output);
}