rhai_components 0.5.3

JSX-like syntax extension for Rhai
Documentation
mod attribute;
mod attribute_value;
mod combine_output_symbols;
mod combine_tag_stack;
pub mod component_meta_module;
pub mod component_reference;
pub mod component_reference_stub;
pub mod component_registry;
mod eval_tag;
mod eval_tag_stack_node;
pub mod evaluator_factory;
mod expression_collection;
mod expression_reference;
mod output_combined_symbol;
mod output_semantic_symbol;
mod output_symbol;
pub mod parse_component;
mod parser_state;
mod tag;
pub mod tag_name;
pub mod tag_stack_node;

#[cfg(test)]
mod tests {
    use std::sync::Arc;

    use anyhow::Result;
    use dashmap::DashSet;
    use rhai::CustomType;
    use rhai::Dynamic;
    use rhai::Engine;
    use rhai::Func;
    use rhai::Map;
    use rhai::TypeBuilder;
    use rhai::module_resolvers::FileModuleResolver;

    use super::component_meta_module::ComponentMetaModule;
    use super::component_reference::ComponentReference;
    use super::component_registry::ComponentRegistry;
    use super::evaluator_factory::EvaluatorFactory;
    use super::parse_component::parse_component;

    #[derive(Clone, Default)]
    struct DummyAssetCollection {
        assets: Arc<DashSet<String>>,
    }

    impl DummyAssetCollection {
        fn rhai_add(&mut self, asset: String) {
            self.assets.insert(asset);
        }
    }

    impl CustomType for DummyAssetCollection {
        fn build(mut builder: TypeBuilder<Self>) {
            builder
                .with_name("DummyAssetCollection")
                .with_fn("add", Self::rhai_add);
        }
    }

    #[derive(Clone, Default)]
    struct DummyContext {
        assets: DummyAssetCollection,
    }

    impl DummyContext {
        fn rhai_assets(&mut self) -> DummyAssetCollection {
            self.assets.clone()
        }
    }

    impl CustomType for DummyContext {
        fn build(mut builder: TypeBuilder<Self>) {
            builder
                .with_name("DummyContext")
                .with_get("assets", Self::rhai_assets);
        }
    }

    #[tokio::test]
    async fn test_docs_parser() -> Result<()> {
        let component_context = DummyContext::default();
        let component_registry = Arc::new(ComponentRegistry::default());

        component_registry.register_component(ComponentReference {
            global_fn_name: "LayoutHomepage_123".to_string(),
            name: "LayoutHomepage".to_string(),
            path: "LayoutHomepage".to_string(),
        });

        component_registry.register_component(ComponentReference {
            global_fn_name: "Note_123".to_string(),
            name: "Note".to_string(),
            path: "Note".to_string(),
        });

        let evaluator_factory = EvaluatorFactory {
            component_registry: component_registry.clone(),
        };

        let mut engine = Engine::new();

        engine.set_fail_on_invalid_map_property(true);
        engine.set_max_expr_depths(256, 256);
        engine.set_module_resolver(FileModuleResolver::new_with_path(format!(
            "{}/src/rhai_components/fixtures",
            env!("CARGO_MANIFEST_DIR")
        )));

        engine.register_custom_syntax_without_look_ahead_raw(
            "component",
            parse_component,
            true,
            evaluator_factory.create_component_evaluator(),
        );

        engine.build_type::<DummyAssetCollection>();
        engine.build_type::<DummyContext>();

        let meta_module =
            ComponentMetaModule::from(component_registry.clone()).into_global_module(&engine)?;

        engine.register_global_module(meta_module.into());

        let renderer = Func::<(DummyContext, Dynamic, Dynamic), String>::create_from_script(
            engine,
            r#"
                import "LayoutHomepage" as LayoutHomepage;
                import "Note" as Note;

                fn template(context, props, content) {
                    context.assets.add("resouces/controller_foo.tsx");

                    component {
                        <!DOCTYPE html>
                        <LayoutHomepage extraBodyClass="my-extra-class">
                            < button
                                class="myclass"
                                data-foo={props.bar}
                                data-fooz={`${props.bar}`}
                                data-gooz={if true {
                                    component {
                                        <div />
                                    }
                                } else {
                                    ":)"
                                }}
                                disabled
                            >
                                <b><i><u>test</u></i></b>
                                Hello! :D
                                {" - "}
                                <br />

                                <Note type="warn">
                                    {if content.is_empty() {
                                        component {
                                            <div>
                                                NOTE EMPTY CONTENT
                                            </div>
                                        }
                                    } else {
                                        content
                                    }}
                                </Note>
                            </button>
                        </LayoutHomepage>
                    }
                }
            "#,
            "template",
        )?;

        println!(
            "{}",
            renderer(
                component_context.clone(),
                Dynamic::from_map({
                    let mut props = Map::new();

                    props.insert("bar".into(), "baz".into());

                    props
                }),
                Dynamic::from(""),
            )?
        );

        assert!(
            component_context
                .assets
                .assets
                .contains("resouces/controller_foo.tsx")
        );
        // assert!(false);

        Ok(())
    }
}