Skip to content

Commit

Permalink
[Fix] Elements Ordering & Struct Field Visibility (render-rs#17)
Browse files Browse the repository at this point in the history
> * As per render-rs#12 prior to this PR you couldn't use generated components across modules as the visibility modifier didn't flow down into each field of the struct. This PR fixes this issue including a test and some documentation about how and why it works.
> * As per render-rs#16 prior to this PR when there were more than 2 children descending an element the order was being garbled due to the way it was being folded together.
> 
> Tests included helping illustrate what was going on.
  • Loading branch information
theashguy authored May 25, 2020
1 parent ebaddfd commit dca74ba
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target
**/*.rs.bk
.DS_Store
41 changes: 34 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ XML rendering, but can work with other usages as well, like ReasonML's [`Pastel`
A renderable component is a struct that implements the `Render` trait. There
are multiple macros that provide a better experience implementing Renderable:

* `#[component]` for defining components using a function
* `rsx!` for composing elements with JSX ergonomics
* `html!` for composing elements and render them to a string
- `#[component]` for defining components using a function
- `rsx!` for composing elements with JSX ergonomics
- `html!` for composing elements and render them to a string

## Why is this different from...

Expand Down Expand Up @@ -113,10 +113,37 @@ assert_eq!(rendered_html, r#"<h1 class="title">Hello world!</h1>"#);

If you pay close attention, you see that the function `Heading` is:

* declared with an uppercase. Underneath, it generates a struct with the same name, and
implements the `Render` trait on it.
* does not have a return type. This is because everything is written to a writer, for
performance reasons.
- declared with an uppercase. Underneath, it generates a struct with the same name, and
implements the `Render` trait on it.
- does not have a return type. This is because everything is written to a writer, for
performance reasons.

### Visibility & Component Libraries

Often you're going to want to store your components somewhere else in your
project tree other than the module you're working on (if not in a different
module entirely!). In these cases, the visibility applied top the function that
defines your component will flow down into all fields of that struct.

For example, if we add "pub" to the front of our Heading component above:

```rust
#[component]
pub fn Heading<'title>(title: &'title str) {
rsx! { <h1 class={"title"}>{title}</h1> }
}
```

...the struct that is generated would look something like...

```rust
pub struct Heading {
pub title: &'title str
}
```

This is important to understand from a safety point of view when structuring
your libraries.

#### Full example

Expand Down
7 changes: 5 additions & 2 deletions render_macros/src/children.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,19 @@ impl Children {
quote! { #child }
})
.collect();

match children_quotes.len() {
0 => quote! { Option::<()>::None },
1 => quote! { Some(#(#children_quotes)*) },
1 => quote! { Some(#(#children_quotes),*) },
_ => {
let mut iter = children_quotes.iter();

let first = iter.next().unwrap();
let second = iter.next().unwrap();

let tuple_of_tuples = iter.fold(
quote!((#first, #second)),
|renderable, current| quote!((#current, #renderable)),
|renderable, current| quote!((#renderable, #current)),
);

quote! { Some(#tuple_of_tuples) }
Expand Down
6 changes: 5 additions & 1 deletion render_macros/src/function_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ pub fn create_function_component(f: syn::ItemFn) -> TokenStream {
let vis = f.vis;

let inputs_block = if inputs.len() > 0 {
quote!({ #inputs })
let input_names: Vec<_> = inputs
.iter()
.collect();

quote!({ #(#vis #input_names),* })
} else {
quote!(;)
};
Expand Down
83 changes: 83 additions & 0 deletions render_tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,36 @@ pub fn works_with_raw() {
assert_eq!(actual, "<div><Hello /></div>");
}

#[test]
pub fn element_ordering() {
use pretty_assertions::assert_eq;
use render::{html, raw};

let actual = html! {
<ul>
<li>{"1"}</li>
<li>{"2"}</li>
<li>{"3"}</li>
</ul>
};

assert_eq!(actual, "<ul><li>1</li><li>2</li><li>3</li></ul>");

let deep = html! {
<div>
<h1>{"A list"}</h1>
<hr />
<ul>
<li>{"1"}</li>
<li>{"2"}</li>
<li>{"3"}</li>
</ul>
</div>
};

assert_eq!(deep, "<div><h1>A list</h1><hr/><ul><li>1</li><li>2</li><li>3</li></ul></div>");
}

mod kaki {
// A simple HTML 5 doctype declaration
use render::html::HTML5Doctype;
Expand Down Expand Up @@ -67,4 +97,57 @@ mod kaki {
);
assert_eq!(actual, expected);
}

#[test]
fn externals_test() {
use pretty_assertions::assert_eq;
use crate::other::ExternalPage;

let actual = render::html! {
<ExternalPage title={"Home"} subtitle={"Foo"}>
{format!("Welcome, {}", "Gal")}
</ExternalPage>
};

let expected = concat!(
"<!DOCTYPE html>",
"<html>",
"<head><title>Home</title></head>",
"<body>",
"<h1>Foo</h1>",
"Welcome, Gal",
"</body>",
"</html>"
);
assert_eq!(actual, expected);
}
}

/// ## Other
///
/// Module for testing component visibility when imported from other modules.
mod other {
use render::html::HTML5Doctype;
use render::{ component, rsx, Render };

#[component]
pub fn ExternalPage<'title, 'subtitle, Children: Render>(
title: &'title str,
subtitle: &'subtitle str,
children: Children
) {
rsx! {
<>
<HTML5Doctype />
<html>
<head><title>{title}</title></head>
<body>
<h1>{subtitle}</h1>
{children}
</body>
</html>
</>
}
}
}

0 comments on commit dca74ba

Please sign in to comment.