merman-core 0.8.0

Mermaid parser + semantic model (headless; parity-focused).
Documentation
use super::*;
use crate::{MermaidConfig, ParseMetadata};

fn meta() -> ParseMetadata {
    ParseMetadata {
        diagram_type: "classDiagram".to_string(),
        config: MermaidConfig::default(),
        effective_config: MermaidConfig::default(),
        title: None,
    }
}

#[test]
fn authoritative_parser_preserves_the_former_fast_subset() {
    let code = r#"classDiagram
class C1 {
  +String field1
  +method1()
}
C1 <|-- C2 : inherits
"#;
    let meta = meta();
    let compat = parse::parse_class(code, &meta).expect("compat parse");
    let typed = parse::parse_class_typed(code, &meta).expect("typed parse");
    assert_eq!(compat, render_model_to_compat_json(&typed, &meta).unwrap());
}

#[test]
fn typed_relations_distinguish_authored_none_from_the_compatibility_sentinel() {
    let code = r#"classDiagram
class A
class B
A --> B
A "none" --> B
A "" --> B
"#;

    let model = parse::parse_class_typed(code, &meta()).expect("class diagram should parse");

    assert_eq!(model.relations[0].relation_title_1, None);
    assert_eq!(model.relations[1].relation_title_1.as_deref(), Some("none"));
    assert_eq!(model.relations[2].relation_title_1.as_deref(), Some(""));

    let typed_json = serde_json::to_value(&model).unwrap();
    assert!(typed_json["relations"][0]["relationTitle1"].is_null());
    assert_eq!(typed_json["relations"][1]["relationTitle1"], "none");
    let round_trip: crate::models::class_diagram::ClassDiagram =
        serde_json::from_value(typed_json).unwrap();
    assert_eq!(round_trip, model);

    let compatibility = render_model_to_compat_json(&model, &meta()).unwrap();
    assert_eq!(compatibility["relations"][0]["relationTitle1"], "none");
    assert_eq!(compatibility["relations"][1]["relationTitle1"], "none");
    assert_eq!(compatibility["relations"][2]["relationTitle1"], "");
}

#[test]
fn synthetic_note_and_interface_ids_do_not_collide_with_authored_classes() {
    let code = r#"classDiagram
class note0
note "standalone"
class interface0
IService ()-- Service
"#;

    let model = parse::parse_class_typed(code, &meta()).expect("class diagram should parse");

    assert!(model.classes.contains_key("note0"));
    assert!(model.classes.contains_key("interface0"));
    assert_ne!(model.notes[0].id, "note0");
    assert_ne!(model.interfaces[0].id, "interface0");
    assert_eq!(model.relations[0].id1, model.interfaces[0].id);
    assert_eq!(model.relations[0].id2, "Service");
}

#[test]
fn namespace_qualified_relation_endpoints_create_facade_classes_like_mermaid() {
    let code = r#"classDiagram
namespace Platform["Platform Layer"] {
  namespace FFI {
    class DartBinding
    class PythonBinding
  }
  namespace Core {
    class Renderer
  }
}
Platform.FFI.DartBinding --> Platform.Core.Renderer : calls
Platform.FFI.PythonBinding --> Platform.Core.Renderer : calls
"#;

    let model = parse::parse_class_typed(code, &meta()).expect("class diagram should parse");

    assert_eq!(
        model.classes.keys().cloned().collect::<Vec<_>>(),
        vec![
            "DartBinding",
            "PythonBinding",
            "Renderer",
            "Platform.FFI.DartBinding",
            "Platform.Core.Renderer",
            "Platform.FFI.PythonBinding"
        ]
    );
    assert_eq!(model.relations[0].id1, "Platform.FFI.DartBinding");
    assert_eq!(model.relations[0].id2, "Platform.Core.Renderer");
    assert_eq!(model.relations[1].id1, "Platform.FFI.PythonBinding");
    assert_eq!(model.relations[1].id2, "Platform.Core.Renderer");
    assert_eq!(
        model.namespaces["Platform.FFI"].class_ids,
        vec!["DartBinding", "PythonBinding"]
    );
    assert_eq!(
        model.namespaces["Platform.Core"].class_ids,
        vec!["Renderer"]
    );
    assert_eq!(
        model.namespace_facade_aliases,
        std::collections::BTreeMap::from([
            ("Platform.Core.Renderer".to_string(), "Renderer".to_string(),),
            (
                "Platform.FFI.DartBinding".to_string(),
                "DartBinding".to_string(),
            ),
            (
                "Platform.FFI.PythonBinding".to_string(),
                "PythonBinding".to_string(),
            ),
        ])
    );

    let typed_json = serde_json::to_value(&model).expect("typed class model should serialize");
    assert_eq!(
        typed_json["namespaceFacadeAliases"]["Platform.FFI.DartBinding"],
        "DartBinding"
    );
    let round_trip: crate::models::class_diagram::ClassDiagram =
        serde_json::from_value(typed_json).expect("typed class model should deserialize");
    assert_eq!(round_trip, model);

    let compatibility =
        render_model_to_compat_json(&model, &meta()).expect("compatibility projection should work");
    assert!(compatibility.get("namespaceFacadeAliases").is_none());
}

#[test]
fn explicit_qualified_class_is_not_a_synthetic_namespace_facade() {
    let code = r#"classDiagram
namespace N {
  class C
}
class N.C["Distinct"]
class D
N.C --> D
"#;

    let model = parse::parse_class_typed(code, &meta()).expect("class diagram should parse");

    assert!(model.namespace_facade_aliases.is_empty());
    assert_eq!(model.classes["N.C"].text, "Distinct");
    assert_eq!(model.relations[0].id1, "N.C");
}