gpui-form-derive 0.7.0

Macro crate for gpui-form
Documentation
use super::support::*;

#[test]
fn test_type_override_and_conversions() {
    let tokens = quote! {
        #[derive(GpuiForm)]
        struct TestForm {
            #[gpui_form(component(
                crate::DatePickerState,
                value(
                    type = chrono::NaiveDate,
                    from_source = |ts| to_form(ts),
                    into_source = |dt| to_model(dt),
                )
            ))]
            birth_date: Option<Timestamp>,
        }
    };

    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let expanded = expansion::expand_gpui_form(
        derive_input,
        structs::GpuiFormOptions {
            generate_shape: true,
            generate_mcp: false,
        },
    );

    let compact = compact_tokens(&expanded.to_string());

    assert!(
        compact.contains(
            "FieldVariant::component_for_field(\"birth_date\",::gpui_form::schema::registry::FieldValueSpec::new(::gpui_form::schema::registry::RustType::from_macro_tokens_unchecked(\"chrono::NaiveDate\"),::gpui_form::schema::registry::RustType::from_macro_tokens_unchecked(\"Timestamp\"),::gpui_form::schema::registry::FieldValuePresence::Optional)"
        ),
        "FieldVariant should use override type for metadata"
    );

    assert!(
        compact.contains("birth_date:from.birth_date.map(") && compact.contains("to_form"),
        "From<Original> for FormValueHolder should apply `from` conversion"
    );

    assert!(
        compact.contains("birth_date:from.birth_date.map(") && compact.contains("to_model"),
        "From<FormValueHolder> for Original should apply `into` conversion"
    );
    assert!(
        compact.contains("fnassert_source_to_form<F>(_:F)")
            && compact.contains("F:::core::ops::FnOnce(Timestamp)->chrono::NaiveDate")
            && compact.contains("fnassert_form_to_source<F>(_:F)")
            && compact.contains("F:::core::ops::FnOnce(chrono::NaiveDate)->Timestamp"),
        "Generated holders should assert explicit conversion signatures: {compact}"
    );
    assert!(
        compact.contains("pubfninto_original(self)->TestForm"),
        "Infallible holders should expose into_original(self): {compact}"
    );
    assert!(
        compact.contains("with_holder_conversion(::gpui_form::schema::registry::HolderConversionMetadata::new(::gpui_form::schema::registry::HolderConversionShape::Infallible,false))"),
        "Inventory metadata should publish infallible holder conversion API shape: {compact}"
    );
    assert!(
        !compact.contains("pubfntry_from("),
        "Generated holders should expose TryFrom through the standard trait only"
    );
}

#[test]
fn test_shape_override_keeps_full_type_path() {
    let tokens = quote! {
        #[derive(GpuiForm)]
        struct TestForm {
            #[gpui_form(component(
                crate::NumericShape::<_>,
                value(
                    type = rust_decimal::Decimal,
                    from_source = |value| value,
                    into_source = |value| value,
                )
            ))]
            amount: f64,
        }
    };

    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let expanded = expansion::expand_gpui_form(
        derive_input,
        structs::GpuiFormOptions {
            generate_shape: true,
            generate_mcp: false,
        },
    );

    let compact = compact_tokens(&expanded.to_string());

    assert!(
        compact.contains(
            "FieldVariant::component_for_field(\"amount\",::gpui_form::schema::registry::FieldValueSpec::new(::gpui_form::schema::registry::RustType::from_macro_tokens_unchecked(\"rust_decimal::Decimal\"),::gpui_form::schema::registry::RustType::from_macro_tokens_unchecked(\"f64\"),if<<crate::NumericShape<rust_decimal::Decimal>as::gpui_form::runtime::shape::GpuiFormComponentShapePolicy>::ValueStoragePolicyas::gpui_form::runtime::shape::ComponentValueStoragePolicy>::REQUIRES_VALUE"
        ),
        "FieldVariant should keep the fully-qualified override type in metadata"
    );
    assert!(
        compact.contains(
            "<crate::NumericShape<rust_decimal::Decimal>as::gpui_form::runtime::shape::GpuiComponentShape>::State"
        ),
        "Component shape `_` should resolve to the override type in state metadata"
    );
    assert!(
        compact.contains(
            "FieldComponentVariant::new(::gpui_form::schema::registry::RustPath::from_macro_tokens_unchecked(\"crate::NumericShape<rust_decimal::Decimal>\"))"
        ),
        "Component shape metadata should preserve the fully-qualified override type"
    );
}

#[test]
fn test_skipped_fields_generate_from_original() {
    let tokens = quote! {
        #[derive(GpuiForm)]
        struct TestForm {
            #[gpui_form(component(
                crate::DatePickerState,
                value(
                    type = chrono::NaiveDate,
                    from_source = |ts| to_form(ts),
                    into_source = |dt| to_model(dt),
                )
            ))]
            birth_date: Option<Timestamp>,

            #[gpui_form(skip)]
            skip_me: bool,
        }
    };

    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let expanded = expansion::expand_gpui_form(
        derive_input,
        structs::GpuiFormOptions {
            generate_shape: true,
            generate_mcp: false,
        },
    );

    let compact = compact_tokens(&expanded.to_string());

    assert!(
        !compact.contains("compile_error!"),
        "skip plus a converted component field should not emit a compile_error"
    );
    assert!(
        compact.contains("::core::convert::From<TestForm>forTestFormFormValueHolder",),
        "From<Original> for FormValueHolder should be generated even with skipped fields"
    );
    assert!(
        compact.contains("birth_date:from.birth_date.map(") && compact.contains("to_form"),
        "From<Original> for FormValueHolder should apply `from_source` conversion"
    );
    assert!(
        !compact.contains("::core::convert::From<TestFormFormValueHolder>forTestForm",),
        "Reverse From<FormValueHolder> for Original should remain disabled when skipped fields exist"
    );
    assert!(
        compact.contains(
            "pubfninto_original(self,skip_me:bool)->Result<TestForm,TestFormFormValueHolderConversionError>"
        ),
        "Skipped-field forms should keep strict into_original(self, skipped...) conversion"
    );
    assert!(
        compact.contains("with_holder_conversion(::gpui_form::schema::registry::HolderConversionMetadata::new(::gpui_form::schema::registry::HolderConversionShape::NeedsSkippedFields,true))"),
        "Inventory metadata should record skipped-field holder conversion shape: {compact}"
    );
}

#[test]
fn test_present_fields_uses_typed_converted_values_for_skipped_forms() {
    let tokens = quote! {
        #[derive(GpuiForm)]
        struct TestForm {
            #[gpui_form(component(
                crate::DatePickerState,
                value(
                    type = chrono::NaiveDate,
                    from_source = |ts| to_form(ts),
                    into_source = |dt| to_model(dt),
                )
            ))]
            birth_date: Option<Timestamp>,

            #[gpui_form(skip)]
            skip_me: bool,
        }
    };

    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let expanded = expansion::expand_gpui_form(
        derive_input,
        structs::GpuiFormOptions {
            generate_shape: true,
            generate_mcp: false,
        },
    );

    let compact = compact_tokens(&expanded.to_string());

    assert!(
        compact.contains("pubenumTestFormFormValueHolderPresentField"),
        "Skipped-field value holders should generate a typed present-field enum"
    );
    assert!(
        compact.contains("pubfnpresent_fields(&self)->Vec<TestFormFormValueHolderPresentField>"),
        "Skipped-field value holders should expose typed present_fields() snapshots: {compact}"
    );
    assert!(
        compact.contains("entries.push(TestFormFormValueHolderPresentField::BirthDate((|dt|to_model(dt))(value)));"),
        "present_fields() should apply `into_source` conversion for optional override fields"
    );
}

#[test]
fn test_default_uses_into_conversion() {
    let tokens = quote! {
        #[derive(GpuiForm)]
        struct TestForm {
            #[gpui_form(component(crate::Input, default = "test@example.com"))]
            email: String,
        }
    };

    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let expanded = expansion::expand_gpui_form(
        derive_input,
        structs::GpuiFormOptions {
            generate_shape: true,
            generate_mcp: false,
        },
    );

    let compact = compact_tokens(&expanded.to_string());

    assert!(
        compact.contains("Into::into(\"test@example.com\")"),
        "Default should be wrapped in Into::into for string literals"
    );
}

#[test]
fn test_skipped_forms_with_string_default_emit_typed_default_comparison() {
    let tokens = quote! {
        #[derive(GpuiForm)]
        struct TestForm {
            #[gpui_form(component(crate::Input, default = "test@example.com"))]
            email: String,

            #[gpui_form(skip)]
            skip_me: bool,
        }
    };

    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let expanded = expansion::expand_gpui_form(
        derive_input,
        structs::GpuiFormOptions {
            generate_shape: true,
            generate_mcp: false,
        },
    );

    let compact = compact_tokens(&expanded.to_string());

    assert!(
        compact.contains("::core::convert::From<TestForm>forTestFormFormValueHolder",),
        "Skipped-field forms should generate From<Original> for value holder"
    );
    assert!(
        compact.contains(
            "letdefault_original:String=::core::convert::Into::into(\"test@example.com\")"
        ),
        "From<Original> should emit a typed default value to avoid Into inference ambiguity"
    );
}