hotreload_pattern.rs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161
  1. #![allow(unused)]
  2. use std::collections::HashMap;
  3. use dioxus_core::{
  4. internal::{
  5. FmtSegment, FmtedSegments, HotReloadAttributeValue, HotReloadDynamicAttribute,
  6. HotReloadDynamicNode, HotReloadLiteral, HotReloadedTemplate, NamedAttribute,
  7. },
  8. prelude::{Template, TemplateNode},
  9. TemplateAttribute, VNode,
  10. };
  11. use dioxus_rsx::{
  12. hot_reload::{self, diff_rsx, ChangedRsx, HotReloadResult},
  13. CallBody, HotReloadingContext,
  14. };
  15. use proc_macro2::TokenStream;
  16. use quote::{quote, ToTokens};
  17. use syn::{parse::Parse, spanned::Spanned, token::Token, File};
  18. #[derive(Debug)]
  19. struct Mock;
  20. impl HotReloadingContext for Mock {
  21. fn map_attribute(
  22. element_name_rust: &str,
  23. attribute_name_rust: &str,
  24. ) -> Option<(&'static str, Option<&'static str>)> {
  25. match element_name_rust {
  26. "svg" => match attribute_name_rust {
  27. "width" => Some(("width", Some("style"))),
  28. "height" => Some(("height", Some("style"))),
  29. _ => None,
  30. },
  31. _ => None,
  32. }
  33. }
  34. fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
  35. match element_name_rust {
  36. "svg" => Some(("svg", Some("svg"))),
  37. _ => None,
  38. }
  39. }
  40. }
  41. fn hot_reload_from_tokens(
  42. old: TokenStream,
  43. new: TokenStream,
  44. ) -> Option<HashMap<usize, HotReloadedTemplate>> {
  45. let old: CallBody = syn::parse2(old).unwrap();
  46. let new: CallBody = syn::parse2(new).unwrap();
  47. hotreload_callbody::<Mock>(&old, &new)
  48. }
  49. fn can_hotreload(old: TokenStream, new: TokenStream) -> bool {
  50. hot_reload_from_tokens(old, new).is_some()
  51. }
  52. fn hotreload_callbody<Ctx: HotReloadingContext>(
  53. old: &CallBody,
  54. new: &CallBody,
  55. ) -> Option<HashMap<usize, HotReloadedTemplate>> {
  56. let results = HotReloadResult::new::<Ctx>(&old.body, &new.body, Default::default())?;
  57. Some(results.templates)
  58. }
  59. fn callbody_to_template<Ctx: HotReloadingContext>(
  60. old: &CallBody,
  61. location: &'static str,
  62. ) -> Option<HotReloadedTemplate> {
  63. let mut results = HotReloadResult::new::<Ctx>(&old.body, &old.body, Default::default())?;
  64. Some(results.templates.remove(&0).unwrap())
  65. }
  66. fn base_stream() -> TokenStream {
  67. quote! {
  68. div {
  69. for item in vec![1, 2, 3] {
  70. div { "asasddasdasd" }
  71. }
  72. for item in vec![4, 5, 6] {
  73. div { "asasddasdasd" }
  74. }
  75. }
  76. }
  77. }
  78. fn base() -> CallBody {
  79. syn::parse2(base_stream()).unwrap()
  80. }
  81. #[test]
  82. fn simple_for_loop() {
  83. let old = quote! {
  84. div {
  85. for item in vec![1, 2, 3] {
  86. div { "asasddasdasd" }
  87. }
  88. }
  89. };
  90. let new_valid = quote! {
  91. div {
  92. for item in vec![1, 2, 3] {
  93. div { "asasddasdasd" }
  94. div { "123" }
  95. }
  96. }
  97. };
  98. let new_invalid = quote! {
  99. div {
  100. for item in vec![1, 2, 3, 4] {
  101. div { "asasddasdasd" }
  102. div { "123" }
  103. }
  104. }
  105. };
  106. let location = "file:line:col:0";
  107. let old: CallBody = syn::parse2(old).unwrap();
  108. let new_valid: CallBody = syn::parse2(new_valid).unwrap();
  109. let new_invalid: CallBody = syn::parse2(new_invalid).unwrap();
  110. assert!(hotreload_callbody::<Mock>(&old, &new_valid).is_some());
  111. assert!(hotreload_callbody::<Mock>(&old, &new_invalid).is_none());
  112. }
  113. #[test]
  114. fn valid_reorder() {
  115. let old = base();
  116. let new_valid = quote! {
  117. div {
  118. for item in vec![4, 5, 6] {
  119. span { "asasddasdasd" }
  120. span { "123" }
  121. }
  122. for item in vec![1, 2, 3] {
  123. div { "asasddasdasd" }
  124. div { "123" }
  125. }
  126. }
  127. };
  128. let new: CallBody = syn::parse2(new_valid).unwrap();
  129. let valid = hotreload_callbody::<Mock>(&old, &new);
  130. assert!(valid.is_some());
  131. let templates = valid.unwrap();
  132. // Currently we return all the templates, even if they didn't change
  133. assert_eq!(templates.len(), 3);
  134. let template = &templates[&0];
  135. // It's an inversion, so we should get them in reverse
  136. assert_eq!(
  137. template.roots,
  138. &[TemplateNode::Element {
  139. tag: "div",
  140. namespace: None,
  141. attrs: &[],
  142. children: &[
  143. TemplateNode::Dynamic { id: 0 },
  144. TemplateNode::Dynamic { id: 1 }
  145. ]
  146. }]
  147. );
  148. assert_eq!(
  149. template.dynamic_nodes,
  150. &[
  151. HotReloadDynamicNode::Dynamic(1),
  152. HotReloadDynamicNode::Dynamic(0)
  153. ]
  154. );
  155. }
  156. #[test]
  157. fn valid_new_node() {
  158. // Adding a new dynamic node should be hot reloadable as long as the text was present in the old version
  159. // of the rsx block
  160. let old = quote! {
  161. div {
  162. for item in vec![1, 2, 3] {
  163. div { "item is {item}" }
  164. }
  165. }
  166. };
  167. let new = quote! {
  168. div {
  169. for item in vec![1, 2, 3] {
  170. div { "item is {item}" }
  171. div { "item is also {item}" }
  172. }
  173. }
  174. };
  175. let templates = hot_reload_from_tokens(old, new).unwrap();
  176. // Currently we return all the templates, even if they didn't change
  177. assert_eq!(templates.len(), 2);
  178. let template = &templates[&1];
  179. // The new dynamic node should be created from the formatted segments pool
  180. assert_eq!(
  181. template.dynamic_nodes,
  182. &[
  183. HotReloadDynamicNode::Formatted(FmtedSegments::new(vec![
  184. FmtSegment::Literal { value: "item is " },
  185. FmtSegment::Dynamic { id: 0 }
  186. ],)),
  187. HotReloadDynamicNode::Formatted(FmtedSegments::new(vec![
  188. FmtSegment::Literal {
  189. value: "item is also "
  190. },
  191. FmtSegment::Dynamic { id: 0 }
  192. ],)),
  193. ]
  194. );
  195. }
  196. #[test]
  197. fn valid_new_dynamic_attribute() {
  198. // Adding a new dynamic attribute should be hot reloadable as long as the text was present in the old version
  199. // of the rsx block
  200. let old = quote! {
  201. div {
  202. for item in vec![1, 2, 3] {
  203. div {
  204. class: "item is {item}"
  205. }
  206. }
  207. }
  208. };
  209. let new = quote! {
  210. div {
  211. for item in vec![1, 2, 3] {
  212. div {
  213. class: "item is {item}"
  214. }
  215. div {
  216. class: "item is also {item}"
  217. }
  218. }
  219. }
  220. };
  221. let templates = hot_reload_from_tokens(old, new).unwrap();
  222. // Currently we return all the templates, even if they didn't change
  223. assert_eq!(templates.len(), 2);
  224. let template = &templates[&1];
  225. // We should have a new dynamic attribute
  226. assert_eq!(
  227. template.roots,
  228. &[
  229. TemplateNode::Element {
  230. tag: "div",
  231. namespace: None,
  232. attrs: &[TemplateAttribute::Dynamic { id: 0 }],
  233. children: &[]
  234. },
  235. TemplateNode::Element {
  236. tag: "div",
  237. namespace: None,
  238. attrs: &[TemplateAttribute::Dynamic { id: 1 }],
  239. children: &[]
  240. }
  241. ]
  242. );
  243. // The new dynamic attribute should be created from the formatted segments pool
  244. assert_eq!(
  245. template.dynamic_attributes,
  246. &[
  247. HotReloadDynamicAttribute::Named(NamedAttribute::new(
  248. "class",
  249. None,
  250. HotReloadAttributeValue::Literal(HotReloadLiteral::Fmted(FmtedSegments::new(
  251. vec![
  252. FmtSegment::Literal { value: "item is " },
  253. FmtSegment::Dynamic { id: 0 }
  254. ],
  255. )))
  256. )),
  257. HotReloadDynamicAttribute::Named(NamedAttribute::new(
  258. "class",
  259. None,
  260. HotReloadAttributeValue::Literal(HotReloadLiteral::Fmted(FmtedSegments::new(
  261. vec![
  262. FmtSegment::Literal {
  263. value: "item is also "
  264. },
  265. FmtSegment::Dynamic { id: 0 }
  266. ],
  267. )))
  268. )),
  269. ]
  270. );
  271. }
  272. #[test]
  273. fn valid_move_dynamic_segment_between_nodes() {
  274. // Hot reloading should let you move around a dynamic formatted segment between nodes
  275. let old = quote! {
  276. div {
  277. for item in vec![1, 2, 3] {
  278. div {
  279. class: "item is {item}"
  280. }
  281. }
  282. }
  283. };
  284. let new = quote! {
  285. div {
  286. for item in vec![1, 2, 3] {
  287. "item is {item}"
  288. }
  289. }
  290. };
  291. let templates = hot_reload_from_tokens(old, new).unwrap();
  292. // Currently we return all the templates, even if they didn't change
  293. assert_eq!(templates.len(), 2);
  294. let template = &templates[&1];
  295. // We should have a new dynamic node and no attributes
  296. assert_eq!(template.roots, &[TemplateNode::Dynamic { id: 0 }]);
  297. // The new dynamic node should be created from the formatted segments pool
  298. assert_eq!(
  299. template.dynamic_nodes,
  300. &[HotReloadDynamicNode::Formatted(FmtedSegments::new(vec![
  301. FmtSegment::Literal { value: "item is " },
  302. FmtSegment::Dynamic { id: 0 }
  303. ])),]
  304. );
  305. }
  306. #[test]
  307. fn valid_keys() {
  308. let a = quote! {
  309. div {
  310. key: "{value}",
  311. }
  312. };
  313. // we can clone dynamic nodes to hot reload them
  314. let b = quote! {
  315. div {
  316. key: "{value}-1234",
  317. }
  318. };
  319. let hot_reload = hot_reload_from_tokens(a, b).unwrap();
  320. assert_eq!(hot_reload.len(), 1);
  321. let template = &hot_reload[&0];
  322. assert_eq!(
  323. template.key,
  324. Some(FmtedSegments::new(vec![
  325. FmtSegment::Dynamic { id: 0 },
  326. FmtSegment::Literal { value: "-1234" }
  327. ]))
  328. );
  329. }
  330. #[test]
  331. fn invalid_cases() {
  332. let new_invalid = quote! {
  333. div {
  334. for item in vec![1, 2, 3, 4] {
  335. div { "asasddasdasd" }
  336. div { "123" }
  337. }
  338. for item in vec![4, 5, 6] {
  339. span { "asasddasdasd" }
  340. span { "123" }
  341. }
  342. }
  343. };
  344. // just remove an entire for loop
  345. let new_valid_removed = quote! {
  346. div {
  347. for item in vec![4, 5, 6] {
  348. span { "asasddasdasd" }
  349. span { "123" }
  350. }
  351. }
  352. };
  353. let new_invalid_new_dynamic_internal = quote! {
  354. div {
  355. for item in vec![1, 2, 3] {
  356. div { "asasddasdasd" }
  357. div { "123" }
  358. }
  359. for item in vec![4, 5, 6] {
  360. span { "asasddasdasd" }
  361. // this is a new dynamic node, and thus can't be hot reloaded
  362. // Eventually we might be able to do a format like this, but not right now
  363. span { "123 {item}" }
  364. }
  365. }
  366. };
  367. let new_invalid_added = quote! {
  368. div {
  369. for item in vec![1, 2, 3] {
  370. div { "asasddasdasd" }
  371. div { "123" }
  372. }
  373. for item in vec![4, 5, 6] {
  374. span { "asasddasdasd" }
  375. span { "123" }
  376. }
  377. for item in vec![7, 8, 9] {
  378. span { "asasddasdasd" }
  379. span { "123" }
  380. }
  381. }
  382. };
  383. let location = "file:line:col:0";
  384. let old = base();
  385. let new_invalid: CallBody = syn::parse2(new_invalid).unwrap();
  386. let new_valid_removed: CallBody = syn::parse2(new_valid_removed).unwrap();
  387. let new_invalid_new_dynamic_internal: CallBody =
  388. syn::parse2(new_invalid_new_dynamic_internal).unwrap();
  389. let new_invalid_added: CallBody = syn::parse2(new_invalid_added).unwrap();
  390. assert!(hotreload_callbody::<Mock>(&old, &new_invalid).is_none());
  391. assert!(hotreload_callbody::<Mock>(&old, &new_invalid_new_dynamic_internal).is_none());
  392. let templates = hotreload_callbody::<Mock>(&old, &new_valid_removed).unwrap();
  393. // we don't get the removed template back
  394. assert_eq!(templates.len(), 2);
  395. let template = &templates.get(&0).unwrap();
  396. // We just completely removed the dynamic node, so it should be a "dud" path and then the placement
  397. assert_eq!(
  398. template.roots,
  399. &[TemplateNode::Element {
  400. tag: "div",
  401. namespace: None,
  402. attrs: &[],
  403. children: &[TemplateNode::Dynamic { id: 0 }]
  404. }]
  405. );
  406. assert_eq!(template.dynamic_nodes, &[HotReloadDynamicNode::Dynamic(1)]);
  407. // Adding a new dynamic node should not be hot reloadable
  408. let added = hotreload_callbody::<Mock>(&old, &new_invalid_added);
  409. assert!(added.is_none());
  410. }
  411. #[test]
  412. fn attributes_reload() {
  413. let old = quote! {
  414. div {
  415. class: "{class}",
  416. id: "{id}",
  417. name: "name",
  418. }
  419. };
  420. // Same order, just different contents
  421. let new_valid_internal = quote! {
  422. div {
  423. id: "{id}",
  424. name: "name",
  425. class: "{class}"
  426. }
  427. };
  428. let templates = hot_reload_from_tokens(old, new_valid_internal).unwrap();
  429. dbg!(templates);
  430. }
  431. #[test]
  432. fn template_generates() {
  433. let old = quote! {
  434. svg {
  435. width: 100,
  436. height: "100px",
  437. "width2": 100,
  438. "height2": "100px",
  439. p { "hello world" }
  440. {(0..10).map(|i| rsx!{"{i}"})}
  441. }
  442. div {
  443. width: 120,
  444. div {
  445. height: "100px",
  446. "width2": 130,
  447. "height2": "100px",
  448. for i in 0..10 {
  449. div {
  450. "asdasd"
  451. }
  452. }
  453. }
  454. }
  455. };
  456. let old: CallBody = syn::parse2(old).unwrap();
  457. let template = callbody_to_template::<Mock>(&old, "file:line:col:0");
  458. }
  459. #[test]
  460. fn diffs_complex() {
  461. #[allow(unused, non_snake_case)]
  462. fn Comp() -> dioxus_core::Element {
  463. VNode::empty()
  464. }
  465. let old = quote! {
  466. svg {
  467. width: 100,
  468. height: "100px",
  469. "width2": 100,
  470. "height2": "100px",
  471. p { "hello world" }
  472. {(0..10).map(|i| rsx!{"{i}"})},
  473. {(0..10).map(|i| rsx!{"{i}"})},
  474. {(0..11).map(|i| rsx!{"{i}"})},
  475. Comp {}
  476. }
  477. };
  478. // scrambling the attributes should not cause a full rebuild
  479. let new = quote! {
  480. div {
  481. width: 100,
  482. height: "100px",
  483. "width2": 100,
  484. "height2": "100px",
  485. p { "hello world" }
  486. Comp {}
  487. {(0..10).map(|i| rsx!{"{i}"})},
  488. {(0..10).map(|i| rsx!{"{i}"})},
  489. {(0..11).map(|i| rsx!{"{i}"})},
  490. }
  491. };
  492. let old: CallBody = syn::parse2(old).unwrap();
  493. let new: CallBody = syn::parse2(new).unwrap();
  494. let templates = hotreload_callbody::<Mock>(&old, &new).unwrap();
  495. }
  496. #[test]
  497. fn remove_node() {
  498. let valid = hot_reload_from_tokens(
  499. quote! {
  500. svg {
  501. Comp {}
  502. {(0..10).map(|i| rsx!{"{i}"})},
  503. }
  504. },
  505. quote! {
  506. div {
  507. {(0..10).map(|i| rsx!{"{i}"})},
  508. }
  509. },
  510. )
  511. .unwrap();
  512. dbg!(valid);
  513. }
  514. #[test]
  515. fn if_chains() {
  516. let valid = hot_reload_from_tokens(
  517. quote! {
  518. if cond {
  519. "foo"
  520. }
  521. },
  522. quote! {
  523. if cond {
  524. "baz"
  525. }
  526. },
  527. )
  528. .unwrap();
  529. let very_complex_chain = hot_reload_from_tokens(
  530. quote! {
  531. if cond {
  532. if second_cond {
  533. "foo"
  534. }
  535. } else if othercond {
  536. "bar"
  537. } else {
  538. "baz"
  539. }
  540. },
  541. quote! {
  542. if cond {
  543. if second_cond {
  544. span { "asasddasdasd 789" }
  545. }
  546. } else if othercond {
  547. span { "asasddasdasd 123" }
  548. } else {
  549. span { "asasddasdas 456" }
  550. }
  551. },
  552. )
  553. .unwrap();
  554. dbg!(very_complex_chain);
  555. }
  556. #[test]
  557. fn component_bodies() {
  558. let valid = can_hotreload(
  559. quote! {
  560. Comp {
  561. "foo"
  562. }
  563. },
  564. quote! {
  565. Comp {
  566. "baz"
  567. }
  568. },
  569. );
  570. assert!(valid);
  571. }
  572. // We currently don't track aliasing which means we can't allow dynamic nodes/formatted segments to be moved between scopes
  573. #[test]
  574. fn moving_between_scopes() {
  575. let valid = can_hotreload(
  576. quote! {
  577. for x in 0..10 {
  578. for y in 0..10 {
  579. div { "x is {x}" }
  580. }
  581. }
  582. },
  583. quote! {
  584. for x in 0..10 {
  585. div { "x is {x}" }
  586. }
  587. },
  588. );
  589. assert!(!valid);
  590. }
  591. /// Everything reloads!
  592. #[test]
  593. fn kitch_sink_of_reloadability() {
  594. let valid = hot_reload_from_tokens(
  595. quote! {
  596. div {
  597. for i in 0..10 {
  598. div { "123" }
  599. Comp {
  600. "foo"
  601. }
  602. if cond {
  603. "foo"
  604. }
  605. }
  606. }
  607. },
  608. quote! {
  609. div {
  610. "hi!"
  611. for i in 0..10 {
  612. div { "456" }
  613. Comp { "bar" }
  614. if cond {
  615. "baz"
  616. }
  617. }
  618. }
  619. },
  620. )
  621. .unwrap();
  622. dbg!(valid);
  623. }
  624. /// Moving nodes inbetween multiple rsx! calls currently doesn't work
  625. /// Sad. Needs changes to core to work, and is technically flawed?
  626. #[test]
  627. fn entire_kitchen_sink() {
  628. let valid = hot_reload_from_tokens(
  629. quote! {
  630. div {
  631. for i in 0..10 {
  632. div { "123" }
  633. }
  634. Comp {
  635. "foo"
  636. }
  637. if cond {
  638. "foo"
  639. }
  640. }
  641. },
  642. quote! {
  643. div {
  644. "hi!"
  645. Comp {
  646. for i in 0..10 {
  647. div { "456" }
  648. }
  649. "bar"
  650. if cond {
  651. "baz"
  652. }
  653. }
  654. }
  655. },
  656. );
  657. assert!(valid.is_none());
  658. }
  659. #[test]
  660. fn tokenstreams_and_locations() {
  661. let valid = hot_reload_from_tokens(
  662. quote! {
  663. div { "hhi" }
  664. div {
  665. {rsx! { "hi again!" }},
  666. for i in 0..2 {
  667. "first"
  668. div { "hi {i}" }
  669. }
  670. for i in 0..3 {
  671. "Second"
  672. div { "hi {i}" }
  673. }
  674. if false {
  675. div { "hi again!?" }
  676. } else if true {
  677. div { "its cool?" }
  678. } else {
  679. div { "not nice !" }
  680. }
  681. }
  682. },
  683. quote! {
  684. div { "hhi" }
  685. div {
  686. {rsx! { "hi again!" }},
  687. for i in 0..2 {
  688. "first"
  689. div { "hi {i}" }
  690. }
  691. for i in 0..3 {
  692. "Second"
  693. div { "hi {i}" }
  694. }
  695. if false {
  696. div { "hi again?" }
  697. } else if true {
  698. div { "cool?" }
  699. } else {
  700. div { "nice !" }
  701. }
  702. }
  703. },
  704. );
  705. dbg!(valid);
  706. }
  707. #[test]
  708. fn ide_testcase() {
  709. let valid = hot_reload_from_tokens(
  710. quote! {
  711. div {
  712. div { "hi!!!123 in!stant relo123a1123dasasdasdasdasd" }
  713. for x in 0..5 {
  714. h3 { "For loop contents" }
  715. }
  716. }
  717. },
  718. quote! {
  719. div {
  720. div { "hi!!!123 in!stant relo123a1123dasasdasdasdasd" }
  721. for x in 0..5 {
  722. h3 { "For loop contents" }
  723. }
  724. }
  725. },
  726. );
  727. dbg!(valid);
  728. }
  729. #[test]
  730. fn assigns_ids() {
  731. let toks = quote! {
  732. div {
  733. div { "hi!!!123 in!stant relo123a1123dasasdasdasdasd" }
  734. for x in 0..5 {
  735. h3 { "For loop contents" }
  736. }
  737. }
  738. };
  739. let parsed = syn::parse2::<CallBody>(toks).unwrap();
  740. let node = parsed.body.get_dyn_node(&[0, 1]);
  741. dbg!(node);
  742. }
  743. #[test]
  744. fn simple_start() {
  745. let valid = can_hotreload(
  746. //
  747. quote! {
  748. div {
  749. class: "Some {one}",
  750. id: "Something {two}",
  751. "One"
  752. }
  753. },
  754. quote! {
  755. div {
  756. id: "Something {two}",
  757. class: "Some {one}",
  758. "One"
  759. }
  760. },
  761. );
  762. assert!(valid);
  763. }
  764. #[test]
  765. fn complex_cases() {
  766. let valid = can_hotreload(
  767. quote! {
  768. div {
  769. class: "Some {one}",
  770. id: "Something {two}",
  771. "One"
  772. }
  773. },
  774. quote! {
  775. div {
  776. class: "Some {one}",
  777. id: "Something else {two}",
  778. "One"
  779. }
  780. },
  781. );
  782. assert!(valid);
  783. }
  784. #[test]
  785. fn attribute_cases() {
  786. let valid = can_hotreload(
  787. quote! {
  788. div {
  789. class: "Some {one}",
  790. id: "Something {two}",
  791. "One"
  792. }
  793. },
  794. quote! {
  795. div {
  796. id: "Something {two}",
  797. "One"
  798. }
  799. },
  800. );
  801. assert!(valid);
  802. let valid = can_hotreload(
  803. //
  804. quote! { div { class: 123 } },
  805. quote! { div { class: 456 } },
  806. );
  807. assert!(valid);
  808. let valid = can_hotreload(
  809. //
  810. quote! { div { class: 123.0 } },
  811. quote! { div { class: 456.0 } },
  812. );
  813. assert!(valid);
  814. let valid = can_hotreload(
  815. //
  816. quote! { div { class: "asd {123}", } },
  817. quote! { div { class: "def", } },
  818. );
  819. assert!(valid);
  820. }
  821. #[test]
  822. fn text_node_cases() {
  823. let valid = can_hotreload(
  824. //
  825. quote! { div { "hello {world}" } },
  826. quote! { div { "world {world}" } },
  827. );
  828. assert!(valid);
  829. let valid = can_hotreload(
  830. //
  831. quote! { div { "hello {world}" } },
  832. quote! { div { "world" } },
  833. );
  834. assert!(valid);
  835. let valid = can_hotreload(
  836. //
  837. quote! { div { "hello {world}" } },
  838. quote! { div { "world {world} {world}" } },
  839. );
  840. assert!(valid);
  841. let valid = can_hotreload(
  842. //
  843. quote! { div { "hello" } },
  844. quote! { div { "world {world}" } },
  845. );
  846. assert!(!valid);
  847. }
  848. #[test]
  849. fn simple_carry() {
  850. let a = quote! {
  851. // start with
  852. "thing {abc} {def}" // 1, 1, 1
  853. "thing {def}" // 1, 0, 1
  854. "other {hij}" // 1, 1, 1
  855. };
  856. let b = quote! {
  857. // end with
  858. "thing {def}"
  859. "thing {abc}"
  860. "thing {hij}"
  861. };
  862. let valid = can_hotreload(a, b);
  863. assert!(valid);
  864. }
  865. #[test]
  866. fn complex_carry_text() {
  867. let a = quote! {
  868. // start with
  869. "thing {abc} {def}" // 1, 1, 1
  870. "thing {abc}" // 1, 0, 1
  871. "other {abc} {def} {hij}" // 1, 1, 1
  872. };
  873. let b = quote! {
  874. // end with
  875. "thing {abc}"
  876. "thing {hij}"
  877. };
  878. let valid = can_hotreload(a, b);
  879. assert!(valid);
  880. }
  881. #[test]
  882. fn complex_carry() {
  883. let a = quote! {
  884. Component {
  885. class: "thing {abc}",
  886. other: "other {abc} {def}",
  887. }
  888. Component {
  889. class: "thing {abc}",
  890. other: "other",
  891. }
  892. };
  893. let b = quote! {
  894. // how about shuffling components, for, if, etc
  895. Component {
  896. class: "thing {abc}",
  897. other: "other {abc} {def}",
  898. }
  899. Component {
  900. class: "thing",
  901. other: "other",
  902. }
  903. };
  904. let valid = can_hotreload(a, b);
  905. assert!(valid);
  906. }
  907. #[test]
  908. fn component_with_lits() {
  909. let a = quote! {
  910. Component {
  911. class: 123,
  912. id: 456.789,
  913. other: true,
  914. blah: "hello {world}",
  915. }
  916. };
  917. // changing lit values
  918. let b = quote! {
  919. Component {
  920. class: 456,
  921. id: 789.456,
  922. other: false,
  923. blah: "goodbye {world}",
  924. }
  925. };
  926. let valid = can_hotreload(a, b);
  927. assert!(valid);
  928. }
  929. #[test]
  930. fn component_with_handlers() {
  931. let a = quote! {
  932. Component {
  933. class: 123,
  934. id: 456.789,
  935. other: true,
  936. blah: "hello {world}",
  937. onclick: |e| { println!("clicked") },
  938. }
  939. };
  940. // changing lit values
  941. let b = quote! {
  942. Component {
  943. class: 456,
  944. id: 789.456,
  945. other: false,
  946. blah: "goodbye {world}",
  947. onclick: |e| { println!("clicked") },
  948. }
  949. };
  950. let hot_reload = hot_reload_from_tokens(a, b).unwrap();
  951. let template = hot_reload.get(&0).unwrap();
  952. assert_eq!(
  953. template.component_values,
  954. &[
  955. HotReloadLiteral::Int(456),
  956. HotReloadLiteral::Float(789.456),
  957. HotReloadLiteral::Bool(false),
  958. HotReloadLiteral::Fmted(FmtedSegments::new(vec![
  959. FmtSegment::Literal { value: "goodbye " },
  960. FmtSegment::Dynamic { id: 0 }
  961. ])),
  962. ]
  963. );
  964. }
  965. #[test]
  966. fn duplicating_dynamic_nodes() {
  967. let a = quote! {
  968. div {
  969. {some_expr}
  970. }
  971. };
  972. // we can clone dynamic nodes to hot reload them
  973. let b = quote! {
  974. div {
  975. {some_expr}
  976. {some_expr}
  977. }
  978. };
  979. let valid = can_hotreload(a, b);
  980. assert!(valid);
  981. }
  982. #[test]
  983. fn duplicating_dynamic_attributes() {
  984. let a = quote! {
  985. div {
  986. width: value,
  987. }
  988. };
  989. // we can clone dynamic nodes to hot reload them
  990. let b = quote! {
  991. div {
  992. width: value,
  993. height: value,
  994. }
  995. };
  996. let valid = can_hotreload(a, b);
  997. assert!(valid);
  998. }
  999. // We should be able to fill in empty nodes
  1000. #[test]
  1001. fn valid_fill_empty() {
  1002. let valid = can_hotreload(
  1003. quote! {},
  1004. quote! {
  1005. div { "x is 123" }
  1006. },
  1007. );
  1008. assert!(valid);
  1009. }
  1010. // We should be able to hot reload spreads
  1011. #[test]
  1012. fn valid_spread() {
  1013. let valid = can_hotreload(
  1014. quote! {
  1015. div {
  1016. ..spread
  1017. }
  1018. },
  1019. quote! {
  1020. div {
  1021. "hello world"
  1022. }
  1023. h1 {
  1024. ..spread
  1025. }
  1026. },
  1027. );
  1028. assert!(valid);
  1029. }