dioxus-field 0.1.0

A form-library-agnostic field convention for Dioxus
Documentation
use std::rc::Rc;

use dioxus::prelude::*;
use dioxus_core::AttributeValue;
use dioxus_field::{FieldMetaOverrides, FieldMetaValues, FocusRequest, use_field_meta_state};

fn attribute_text(attributes: &[Attribute], name: &str) -> Option<String> {
    attributes
        .iter()
        .find(|attribute| attribute.name == name)
        .and_then(|attribute| match &attribute.value {
            AttributeValue::Text(value) => Some(value.clone()),
            AttributeValue::Bool(value) => Some(value.to_string()),
            _ => None,
        })
}

#[test]
fn field_meta_attributes_derive_accessibility_and_state_with_flag_overrides() {
    #[allow(
        clippy::needless_pass_by_value,
        reason = "VirtualDom entrypoints receive their root properties by value"
    )]
    fn app(attributes: Rc<std::cell::RefCell<Vec<Attribute>>>) -> Element {
        let mut meta = use_field_meta_state(FieldMetaValues {
            id: Some(Rc::from("account-email")),
            name: Some(Rc::from("email")),
            required: true,
            disabled: true,
            errors: vec![Rc::from("Enter a valid email")],
            touched: true,
            dirty: true,
            ..FieldMetaValues::default()
        });
        let _description = meta.register_description_id(Rc::from("email-help"));
        let _error = meta.register_error_id(Rc::from("email-error"));

        attributes
            .borrow_mut()
            .extend(meta.attributes_with(FieldMetaOverrides {
                invalid: Some(false),
                disabled: Some(false),
            }));

        VNode::empty()
    }

    let attributes = Rc::new(std::cell::RefCell::new(Vec::new()));
    VirtualDom::new_with_props(app, Rc::clone(&attributes)).rebuild_in_place();
    let attributes = attributes.borrow();

    assert_eq!(
        attribute_text(&attributes, "id").as_deref(),
        Some("account-email")
    );
    assert_eq!(
        attribute_text(&attributes, "name").as_deref(),
        Some("email")
    );
    assert_eq!(
        attribute_text(&attributes, "aria-invalid").as_deref(),
        Some("false")
    );
    assert_eq!(
        attribute_text(&attributes, "aria-describedby").as_deref(),
        Some("email-help")
    );
    assert_eq!(attribute_text(&attributes, "aria-errormessage"), None);
    assert_eq!(attribute_text(&attributes, "disabled"), None);
    assert_eq!(
        attribute_text(&attributes, "data-touched").as_deref(),
        Some("true")
    );
    assert_eq!(
        attribute_text(&attributes, "data-dirty").as_deref(),
        Some("true")
    );
}

#[test]
fn focus_request_tracks_the_current_lifecycle_registration() {
    #[allow(
        clippy::needless_pass_by_value,
        reason = "VirtualDom entrypoints receive their root properties by value"
    )]
    fn app(requests: Rc<std::cell::Cell<usize>>) -> Element {
        let first_requests = Rc::clone(&requests);
        let second_requests = Rc::clone(&requests);
        let request = FocusRequest::default();
        let first = request.register(Callback::new(move |()| {
            first_requests.set(first_requests.get() + 1);
        }));

        assert!(request.request());
        let second = request.register(Callback::new(move |()| {
            second_requests.set(second_requests.get() + 10);
        }));
        drop(first);
        assert!(request.request());
        drop(second);
        assert!(!request.request());

        VNode::empty()
    }

    let requests = Rc::new(std::cell::Cell::new(0));
    VirtualDom::new_with_props(app, Rc::clone(&requests)).rebuild_in_place();
    assert_eq!(requests.get(), 11);
}