csharp-rs 0.1.2

Generate C# type definitions from Rust structs and enums
Documentation
// Rust guideline compliant 2026-02-10
//! Integration tests for `#[derive(CSharp)]` on enums with unit variants.

#![expect(dead_code, reason = "test enums are only used via derive macro")]

use csharp_rs::{CSharp, CSharpVersion, Config, Serializer};

// --- simple enum ---

#[derive(CSharp)]
enum Color {
    Red,
    Green,
    Blue,
}

#[test]
fn simple_enum_name() {
    let cfg = Config::default();
    assert_eq!(Color::csharp_name(&cfg), "Color");
}

#[test]
fn simple_enum_definition_contains_enum() {
    let cfg = Config::default();
    let def = Color::csharp_definition(&cfg);
    assert!(
        def.contains("public enum Color"),
        "missing enum declaration:\n{def}"
    );
}

#[test]
fn simple_enum_definition_has_variants() {
    let cfg = Config::default();
    let def = Color::csharp_definition(&cfg);
    assert!(def.contains("Red,"), "missing Red variant:\n{def}");
    assert!(def.contains("Green,"), "missing Green variant:\n{def}");
    assert!(def.contains("Blue,"), "missing Blue variant:\n{def}");
}

#[test]
fn simple_enum_no_enum_member_when_no_renaming() {
    let cfg = Config::default();
    let def = Color::csharp_definition(&cfg);
    assert!(
        !def.contains("EnumMember"),
        "should not have EnumMember without renaming:\n{def}"
    );
}

#[test]
fn simple_enum_has_json_converter() {
    let cfg = Config::default();
    let def = Color::csharp_definition(&cfg);
    assert!(
        def.contains("[JsonConverter(typeof(JsonStringEnumConverter))]"),
        "missing JsonConverter attribute:\n{def}"
    );
}

#[test]
fn simple_enum_has_auto_generated_comment() {
    let cfg = Config::default();
    let def = Color::csharp_definition(&cfg);
    assert!(
        def.starts_with("// <auto-generated/>"),
        "missing auto-generated comment:\n{def}"
    );
}

#[test]
fn simple_enum_has_using_directives() {
    let cfg = Config::default();
    let def = Color::csharp_definition(&cfg);
    assert!(
        def.contains("using System.Runtime.Serialization;"),
        "missing System.Runtime.Serialization using:\n{def}"
    );
    assert!(
        def.contains("using System.Text.Json.Serialization;"),
        "missing System.Text.Json using:\n{def}"
    );
}

#[test]
fn simple_enum_default_namespace() {
    let cfg = Config::default();
    let def = Color::csharp_definition(&cfg);
    assert!(
        def.contains("namespace Generated"),
        "missing default namespace:\n{def}"
    );
}

// --- enum with rename_all ---

#[derive(CSharp)]
#[serde(rename_all = "camelCase")]
enum Status {
    InProgress,
    AlreadyDone,
    NotStarted,
}

#[test]
fn rename_all_camel_case_json_names() {
    let cfg = Config::default();
    let def = Status::csharp_definition(&cfg);
    assert!(
        def.contains("[EnumMember(Value = \"inProgress\")]"),
        "missing camelCase EnumMember for InProgress:\n{def}"
    );
    assert!(
        def.contains("[EnumMember(Value = \"alreadyDone\")]"),
        "missing camelCase EnumMember for AlreadyDone:\n{def}"
    );
    assert!(
        def.contains("[EnumMember(Value = \"notStarted\")]"),
        "missing camelCase EnumMember for NotStarted:\n{def}"
    );
}

#[test]
fn rename_all_keeps_pascal_case_variant_names() {
    let cfg = Config::default();
    let def = Status::csharp_definition(&cfg);
    assert!(
        def.contains("InProgress,"),
        "C# variant should be PascalCase:\n{def}"
    );
    assert!(
        def.contains("AlreadyDone,"),
        "C# variant should be PascalCase:\n{def}"
    );
}

// --- enum with per-variant rename ---

#[derive(CSharp)]
enum Direction {
    #[serde(rename = "up")]
    North,
    #[serde(rename = "down")]
    South,
    East,
    West,
}

#[test]
fn per_variant_rename() {
    let cfg = Config::default();
    let def = Direction::csharp_definition(&cfg);
    assert!(
        def.contains("[EnumMember(Value = \"up\")]"),
        "missing rename for North:\n{def}"
    );
    assert!(
        def.contains("North,"),
        "C# name should remain North:\n{def}"
    );
    assert!(
        def.contains("[EnumMember(Value = \"down\")]"),
        "missing rename for South:\n{def}"
    );
    assert!(def.contains("East,"), "East should be present:\n{def}");
}

// --- enum with skip ---

#[derive(CSharp)]
enum Fruit {
    Apple,
    #[serde(skip)]
    Internal,
    Banana,
}

#[test]
fn skip_variant_excluded() {
    let cfg = Config::default();
    let def = Fruit::csharp_definition(&cfg);
    assert!(def.contains("Apple,"), "Apple should be present:\n{def}");
    assert!(def.contains("Banana,"), "Banana should be present:\n{def}");
    assert!(
        !def.contains("Internal"),
        "Internal should be skipped:\n{def}"
    );
}

// --- enum with namespace ---

#[derive(CSharp)]
#[csharp(namespace = "Game.Types")]
enum Priority {
    Low,
    Medium,
    High,
}

#[test]
fn enum_namespace_override() {
    let cfg = Config::default();
    let def = Priority::csharp_definition(&cfg);
    assert!(
        def.contains("namespace Game.Types"),
        "missing namespace override:\n{def}"
    );
}

// --- combined enum attrs ---

#[derive(CSharp)]
#[serde(rename_all = "snake_case")]
#[csharp(namespace = "Api.Models")]
enum EventType {
    UserLogin,
    #[serde(skip)]
    Internal,
    #[serde(rename = "page_hit")]
    PageView,
    SessionEnd,
}

#[test]
fn combined_enum_attrs() {
    let cfg = Config::default();
    let def = EventType::csharp_definition(&cfg);
    assert!(
        def.contains("namespace Api.Models"),
        "missing namespace:\n{def}"
    );
    assert!(
        def.contains("[EnumMember(Value = \"user_login\")]"),
        "UserLogin should be renamed to snake_case:\n{def}"
    );
    assert!(
        !def.contains("Internal"),
        "Internal should be skipped:\n{def}"
    );
    assert!(
        def.contains("[EnumMember(Value = \"page_hit\")]"),
        "PageView should be renamed to page_hit:\n{def}"
    );
    assert!(
        def.contains("PageView,"),
        "C# name should be PageView:\n{def}"
    );
    assert!(
        def.contains("[EnumMember(Value = \"session_end\")]"),
        "SessionEnd should be renamed to snake_case:\n{def}"
    );
}

// --- enum dependencies ---

#[test]
fn enum_dependencies_empty() {
    let cfg = Config::default();
    let deps = Color::dependencies(&cfg);
    assert!(
        deps.is_empty(),
        "enum should have no dependencies: {deps:?}"
    );
}

// --- multi-config: serializer switching ---

#[test]
fn enum_newtonsoft_uses_newtonsoft_using() {
    let cfg = Config::default().with_serializer(Serializer::Newtonsoft);
    let def = Color::csharp_definition(&cfg);
    assert!(
        def.contains("using Newtonsoft.Json;"),
        "Newtonsoft enum should have Newtonsoft.Json using:\n{def}"
    );
    assert!(
        def.contains("[JsonConverter("),
        "Newtonsoft enum should have [JsonConverter]:\n{def}"
    );
}

#[test]
fn enum_stj_uses_stj_using() {
    let cfg = Config::default().with_serializer(Serializer::SystemTextJson);
    let def = Color::csharp_definition(&cfg);
    assert!(
        def.contains("using System.Text.Json.Serialization;"),
        "STJ enum should have System.Text.Json.Serialization using:\n{def}"
    );
}

// --- multi-config: C# version switching ---

#[test]
fn enum_csharp10_file_scoped_namespace() {
    let cfg = Config::default().with_target(CSharpVersion::CSharp10);
    let def = Color::csharp_definition(&cfg);
    assert!(
        def.contains("namespace Generated;"),
        "C# 10 enum should use file-scoped namespace:\n{def}"
    );
}

#[test]
fn enum_csharp9_block_scoped_namespace() {
    let cfg = Config::default();
    let def = Color::csharp_definition(&cfg);
    assert!(
        !def.contains("namespace Generated;"),
        "C# 9 enum should NOT use file-scoped namespace:\n{def}"
    );
}