123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864 |
- #![allow(unused)]
- use dioxus_core::{prelude::Template, VNode};
- use dioxus_rsx::{
- hot_reload::{diff_rsx, ChangedRsx},
- hotreload::HotReloadedTemplate,
- CallBody, HotReloadingContext,
- };
- use proc_macro2::TokenStream;
- use quote::{quote, ToTokens};
- use syn::{parse::Parse, spanned::Spanned, token::Token, File};
- #[derive(Debug)]
- struct Mock;
- impl HotReloadingContext for Mock {
- fn map_attribute(
- element_name_rust: &str,
- attribute_name_rust: &str,
- ) -> Option<(&'static str, Option<&'static str>)> {
- match element_name_rust {
- "svg" => match attribute_name_rust {
- "width" => Some(("width", Some("style"))),
- "height" => Some(("height", Some("style"))),
- _ => None,
- },
- _ => None,
- }
- }
- fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
- match element_name_rust {
- "svg" => Some(("svg", Some("svg"))),
- _ => None,
- }
- }
- }
- fn boilerplate(old: TokenStream, new: TokenStream) -> Option<Vec<Template>> {
- let old: CallBody = syn::parse2(old).unwrap();
- let new: CallBody = syn::parse2(new).unwrap();
- let location = "file:line:col:0";
- hotreload_callbody::<Mock>(&old, &new, location)
- }
- fn can_hotreload(old: TokenStream, new: TokenStream) -> Option<HotReloadedTemplate> {
- let old: CallBody = syn::parse2(old).unwrap();
- let new: CallBody = syn::parse2(new).unwrap();
- let location = "file:line:col:0";
- let results = HotReloadedTemplate::new::<Mock>(&old, &new, location, Default::default())?;
- Some(results)
- }
- fn hotreload_callbody<Ctx: HotReloadingContext>(
- old: &CallBody,
- new: &CallBody,
- location: &'static str,
- ) -> Option<Vec<Template>> {
- let results = HotReloadedTemplate::new::<Ctx>(old, new, location, Default::default())?;
- Some(results.templates)
- }
- fn callbody_to_template<Ctx: HotReloadingContext>(
- old: &CallBody,
- location: &'static str,
- ) -> Option<Template> {
- let results = HotReloadedTemplate::new::<Ctx>(old, old, location, Default::default())?;
- Some(*results.templates.first().unwrap())
- }
- fn base_stream() -> TokenStream {
- quote! {
- div {
- for item in vec![1, 2, 3] {
- div { "asasddasdasd" }
- }
- for item in vec![4, 5, 6] {
- div { "asasddasdasd" }
- }
- }
- }
- }
- fn base() -> CallBody {
- syn::parse2(base_stream()).unwrap()
- }
- #[test]
- fn simple_for_loop() {
- let old = quote! {
- div {
- for item in vec![1, 2, 3] {
- div { "asasddasdasd" }
- }
- }
- };
- let new_valid = quote! {
- div {
- for item in vec![1, 2, 3] {
- div { "asasddasdasd" }
- div { "123" }
- }
- }
- };
- let new_invalid = quote! {
- div {
- for item in vec![1, 2, 3, 4] {
- div { "asasddasdasd" }
- div { "123" }
- }
- }
- };
- let location = "file:line:col:0";
- let old: CallBody = syn::parse2(old).unwrap();
- let new_valid: CallBody = syn::parse2(new_valid).unwrap();
- let new_invalid: CallBody = syn::parse2(new_invalid).unwrap();
- assert!(hotreload_callbody::<Mock>(&old, &new_valid, location).is_some());
- assert!(hotreload_callbody::<Mock>(&old, &new_invalid, location).is_none());
- }
- #[test]
- fn valid_reorder() {
- let old = base();
- let new_valid = quote! {
- div {
- for item in vec![4, 5, 6] {
- span { "asasddasdasd" }
- span { "123" }
- }
- for item in vec![1, 2, 3] {
- div { "asasddasdasd" }
- div { "123" }
- }
- }
- };
- let location = "file:line:col:0";
- let new: CallBody = syn::parse2(new_valid).unwrap();
- let valid = hotreload_callbody::<Mock>(&old, &new, location);
- assert!(valid.is_some());
- let templates = valid.unwrap();
- // Currently we return all the templates, even if they didn't change
- assert_eq!(templates.len(), 3);
- let template = &templates[2];
- // It's an inversion, so we should get them in reverse
- assert_eq!(template.node_paths, &[&[0, 1], &[0, 0]]);
- // And the byte index should be the original template
- assert_eq!(template.name, "file:line:col:0");
- }
- #[test]
- fn invalid_cases() {
- let new_invalid = quote! {
- div {
- for item in vec![1, 2, 3, 4] {
- div { "asasddasdasd" }
- div { "123" }
- }
- for item in vec![4, 5, 6] {
- span { "asasddasdasd" }
- span { "123" }
- }
- }
- };
- // just remove an entire for loop
- let new_valid_removed = quote! {
- div {
- for item in vec![4, 5, 6] {
- span { "asasddasdasd" }
- span { "123" }
- }
- }
- };
- let new_invalid_new_dynamic_internal = quote! {
- div {
- for item in vec![1, 2, 3] {
- div { "asasddasdasd" }
- div { "123" }
- }
- for item in vec![4, 5, 6] {
- span { "asasddasdasd" }
- // this is a new dynamic node, and thus can't be hot reloaded
- // Eventually we might be able to do a format like this, but not right now
- span { "123 {item}" }
- }
- }
- };
- let new_invalid_added = quote! {
- div {
- for item in vec![1, 2, 3] {
- div { "asasddasdasd" }
- div { "123" }
- }
- for item in vec![4, 5, 6] {
- span { "asasddasdasd" }
- span { "123" }
- }
- for item in vec![7, 8, 9] {
- span { "asasddasdasd" }
- span { "123" }
- }
- }
- };
- let location = "file:line:col:0";
- let old = base();
- let new_invalid: CallBody = syn::parse2(new_invalid).unwrap();
- let new_valid_removed: CallBody = syn::parse2(new_valid_removed).unwrap();
- let new_invalid_new_dynamic_internal: CallBody =
- syn::parse2(new_invalid_new_dynamic_internal).unwrap();
- let new_invalid_added: CallBody = syn::parse2(new_invalid_added).unwrap();
- assert!(hotreload_callbody::<Mock>(&old, &new_invalid, location).is_none());
- assert!(
- hotreload_callbody::<Mock>(&old, &new_invalid_new_dynamic_internal, location).is_none()
- );
- let removed = hotreload_callbody::<Mock>(&old, &new_valid_removed, location);
- assert!(removed.is_some());
- let templates = removed.unwrap();
- // we don't get the removed template back
- assert_eq!(templates.len(), 2);
- let template = &templates[1];
- // We just completely removed the dynamic node, so it should be a "dud" path and then the placement
- assert_eq!(template.node_paths, &[&[], &[0u8, 0] as &[u8]]);
- // Adding a new dynamic node should not be hot reloadable
- let added = hotreload_callbody::<Mock>(&old, &new_invalid_added, location);
- assert!(added.is_none());
- }
- #[test]
- fn new_names() {
- let old = quote! {
- div {
- for item in vec![1, 2, 3] {
- div { "asasddasdasd" }
- div { "123" }
- }
- }
- };
- // Same order, just different contents
- let new_valid_internal = quote! {
- div {
- for item in vec![1, 2, 3] {
- div { "asasddasdasd" }
- div { "456" }
- }
- }
- };
- let templates = boilerplate(old, new_valid_internal).unwrap();
- // Getting back all the templates even though some might not have changed
- // This is currently just a symptom of us not checking if anything has changed, but has no bearing
- // on output really.
- assert_eq!(templates.len(), 2);
- // The ordering is going to be inverse since its a depth-first traversal
- let external = &templates[1];
- assert_eq!(external.name, "file:line:col:0");
- let internal = &templates[0];
- assert_eq!(internal.name, "file:line:col:1");
- }
- #[test]
- fn attributes_reload() {
- let old = quote! {
- div {
- class: "{class}",
- id: "{id}",
- name: "name",
- }
- };
- // Same order, just different contents
- let new_valid_internal = quote! {
- div {
- id: "{id}",
- name: "name",
- class: "{class}"
- }
- };
- let templates = boilerplate(old, new_valid_internal).unwrap();
- dbg!(templates);
- }
- #[test]
- fn template_generates() {
- let old = quote! {
- svg {
- width: 100,
- height: "100px",
- "width2": 100,
- "height2": "100px",
- p { "hello world" }
- {(0..10).map(|i| rsx!{"{i}"})}
- }
- div {
- width: 120,
- div {
- height: "100px",
- "width2": 130,
- "height2": "100px",
- for i in 0..10 {
- div {
- "asdasd"
- }
- }
- }
- }
- };
- let old: CallBody = syn::parse2(old).unwrap();
- let template = callbody_to_template::<Mock>(&old, "file:line:col:0");
- }
- #[test]
- fn diffs_complex() {
- #[allow(unused, non_snake_case)]
- fn Comp() -> dioxus_core::Element {
- VNode::empty()
- }
- let old = quote! {
- svg {
- width: 100,
- height: "100px",
- "width2": 100,
- "height2": "100px",
- p { "hello world" }
- {(0..10).map(|i| rsx!{"{i}"})},
- {(0..10).map(|i| rsx!{"{i}"})},
- {(0..11).map(|i| rsx!{"{i}"})},
- Comp {}
- }
- };
- // scrambling the attributes should not cause a full rebuild
- let new = quote! {
- div {
- width: 100,
- height: "100px",
- "width2": 100,
- "height2": "100px",
- p { "hello world" }
- Comp {}
- {(0..10).map(|i| rsx!{"{i}"})},
- {(0..10).map(|i| rsx!{"{i}"})},
- {(0..11).map(|i| rsx!{"{i}"})},
- }
- };
- let old: CallBody = syn::parse2(old).unwrap();
- let new: CallBody = syn::parse2(new).unwrap();
- let location = "file:line:col:0";
- let templates = hotreload_callbody::<Mock>(&old, &new, location).unwrap();
- }
- #[test]
- fn remove_node() {
- let changed = boilerplate(
- quote! {
- svg {
- Comp {}
- {(0..10).map(|i| rsx!{"{i}"})},
- }
- },
- quote! {
- div {
- {(0..10).map(|i| rsx!{"{i}"})},
- }
- },
- )
- .unwrap();
- dbg!(changed);
- }
- #[test]
- fn if_chains() {
- let changed = boilerplate(
- quote! {
- if cond {
- "foo"
- }
- },
- quote! {
- if cond {
- "baz"
- }
- },
- )
- .unwrap();
- let very_complex_chain = boilerplate(
- quote! {
- if cond {
- if second_cond {
- "foo"
- }
- } else if othercond {
- "bar"
- } else {
- "baz"
- }
- },
- quote! {
- if cond {
- if second_cond {
- span { "asasddasdasd 789" }
- }
- } else if othercond {
- span { "asasddasdasd 123" }
- } else {
- span { "asasddasdas 456" }
- }
- },
- )
- .unwrap();
- dbg!(very_complex_chain);
- }
- #[test]
- fn component_bodies() {
- let changed = boilerplate(
- quote! {
- Comp {
- "foo"
- }
- },
- quote! {
- Comp {
- "baz"
- }
- },
- )
- .unwrap();
- dbg!(changed);
- }
- /// Everything reloads!
- #[test]
- fn kitch_sink_of_reloadability() {
- let changed = boilerplate(
- quote! {
- div {
- for i in 0..10 {
- div { "123" }
- Comp {
- "foo"
- }
- if cond {
- "foo"
- }
- }
- }
- },
- quote! {
- div {
- "hi!"
- for i in 0..10 {
- div { "456" }
- Comp { "bar" }
- if cond {
- "baz"
- }
- }
- }
- },
- )
- .unwrap();
- dbg!(changed);
- }
- /// Moving nodes inbetween multiple rsx! calls currently doesn't work
- /// Sad. Needs changes to core to work, and is technically flawed?
- #[test]
- fn entire_kitchen_sink() {
- let changed = boilerplate(
- quote! {
- div {
- for i in 0..10 {
- div { "123" }
- }
- Comp {
- "foo"
- }
- if cond {
- "foo"
- }
- }
- },
- quote! {
- div {
- "hi!"
- Comp {
- for i in 0..10 {
- div { "456" }
- }
- "bar"
- if cond {
- "baz"
- }
- }
- }
- },
- );
- assert!(changed.is_none());
- }
- #[test]
- fn tokenstreams_and_locations() {
- let changed = boilerplate(
- quote! {
- div { "hhi" }
- div {
- {rsx! { "hi again!" }},
- for i in 0..2 {
- "first"
- div { "hi {i}" }
- }
- for i in 0..3 {
- "Second"
- div { "hi {i}" }
- }
- if false {
- div { "hi again!?" }
- } else if true {
- div { "its cool?" }
- } else {
- div { "not nice !" }
- }
- }
- },
- quote! {
- div { "hhi" }
- div {
- {rsx! { "hi again!" }},
- for i in 0..2 {
- "first"
- div { "hi {i}" }
- }
- for i in 0..3 {
- "Second"
- div { "hi {i}" }
- }
- if false {
- div { "hi again?" }
- } else if true {
- div { "cool?" }
- } else {
- div { "nice !" }
- }
- }
- },
- );
- dbg!(changed);
- }
- #[test]
- fn ide_testcase() {
- let changed = boilerplate(
- quote! {
- div {
- div { "hi!!!123 in!stant relo123a1123dasasdasdasdasd" }
- for x in 0..5 {
- h3 { "For loop contents" }
- }
- }
- },
- quote! {
- div {
- div { "hi!!!123 in!stant relo123a1123dasasdasdasdasd" }
- for x in 0..5 {
- h3 { "For loop contents" }
- }
- }
- },
- );
- dbg!(changed);
- }
- #[test]
- fn assigns_ids() {
- let toks = quote! {
- div {
- div { "hi!!!123 in!stant relo123a1123dasasdasdasdasd" }
- for x in 0..5 {
- h3 { "For loop contents" }
- }
- }
- };
- let parsed = syn::parse2::<CallBody>(toks).unwrap();
- let node = parsed.body.get_dyn_node(&[0, 1]);
- dbg!(node);
- }
- #[test]
- fn simple_start() {
- let changed = boilerplate(
- //
- quote! {
- div {
- class: "Some {one}",
- id: "Something {two}",
- "One"
- }
- },
- quote! {
- div {
- id: "Something {two}",
- class: "Some {one}",
- "One"
- }
- },
- );
- dbg!(changed.unwrap());
- }
- #[test]
- fn complex_cases() {
- let changed = can_hotreload(
- quote! {
- div {
- class: "Some {one}",
- id: "Something {two}",
- "One"
- }
- },
- quote! {
- div {
- class: "Some {one}",
- id: "Something else {two}",
- "One"
- }
- },
- );
- dbg!(changed.unwrap());
- }
- #[test]
- fn attribute_cases() {
- let changed = can_hotreload(
- quote! {
- div {
- class: "Some {one}",
- id: "Something {two}",
- "One"
- }
- },
- quote! {
- div {
- id: "Something {two}",
- "One"
- }
- },
- );
- dbg!(changed.unwrap());
- let changed = can_hotreload(
- //
- quote! { div { class: 123 } },
- quote! { div { class: 456 } },
- );
- dbg!(changed.unwrap());
- let changed = can_hotreload(
- //
- quote! { div { class: 123.0 } },
- quote! { div { class: 456.0 } },
- );
- dbg!(changed.unwrap());
- let changed = can_hotreload(
- //
- quote! { div { class: "asd {123}", } },
- quote! { div { class: "def", } },
- );
- dbg!(changed.unwrap());
- }
- #[test]
- fn text_node_cases() {
- let changed = can_hotreload(
- //
- quote! { div { "hello {world}" } },
- quote! { div { "world {world}" } },
- );
- dbg!(changed.unwrap());
- let changed = can_hotreload(
- //
- quote! { div { "hello {world}" } },
- quote! { div { "world" } },
- );
- dbg!(changed.unwrap());
- let changed = can_hotreload(
- //
- quote! { div { "hello" } },
- quote! { div { "world {world}" } },
- );
- assert!(changed.is_none());
- }
- #[test]
- fn simple_carry() {
- let a = quote! {
- // start with
- "thing {abc} {def}" // 1, 1, 1
- "thing {def}" // 1, 0, 1
- "other {hij}" // 1, 1, 1
- };
- let b = quote! {
- // end with
- "thing {def}"
- "thing {abc}"
- "thing {hij}"
- };
- let changed = can_hotreload(a, b);
- dbg!(changed.unwrap());
- }
- #[test]
- fn complex_carry_text() {
- let a = quote! {
- // start with
- "thing {abc} {def}" // 1, 1, 1
- "thing {abc}" // 1, 0, 1
- "other {abc} {def} {hij}" // 1, 1, 1
- };
- let b = quote! {
- // end with
- "thing {abc}"
- "thing {hij}"
- };
- let changed = can_hotreload(a, b);
- dbg!(changed.unwrap());
- }
- #[test]
- fn complex_carry() {
- let a = quote! {
- Component {
- class: "thing {abc}",
- other: "other {abc} {def}",
- }
- Component {
- class: "thing {abc}",
- other: "other",
- }
- };
- let b = quote! {
- // how about shuffling components, for, if, etc
- Component {
- class: "thing {abc}",
- other: "other {abc} {def}",
- }
- Component {
- class: "thing",
- other: "other",
- }
- };
- let changed = can_hotreload(a, b);
- dbg!(changed.unwrap());
- }
- #[test]
- fn component_with_lits() {
- let a = quote! {
- Component {
- class: 123,
- id: 456.789,
- other: true,
- blah: "hello {world}",
- }
- };
- // changing lit values
- let b = quote! {
- Component {
- class: 456,
- id: 789.456,
- other: false,
- blah: "goodbye {world}",
- }
- };
- let changed = can_hotreload(a, b);
- dbg!(changed.unwrap());
- }
- #[test]
- fn component_with_handlers() {
- let a = quote! {
- Component {
- class: 123,
- id: 456.789,
- other: true,
- blah: "hello {world}",
- onclick: |e| { println!("clicked") },
- }
- };
- // changing lit values
- let b = quote! {
- Component {
- class: 456,
- id: 789.456,
- other: false,
- blah: "goodbye {world}",
- onclick: |e| { println!("clicked") },
- }
- };
- let changed = can_hotreload(a, b);
- dbg!(changed.unwrap());
- }
|