hotreload_pattern.rs 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264
  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 component_remove_key() {
  967. let a = quote! {
  968. Component {
  969. key: "{key}",
  970. class: 123,
  971. id: 456.789,
  972. other: true,
  973. dynamic1,
  974. dynamic2,
  975. blah: "hello {world}",
  976. onclick: |e| { println!("clicked") },
  977. }
  978. };
  979. // changing lit values
  980. let b = quote! {
  981. Component {
  982. class: 456,
  983. id: 789.456,
  984. other: false,
  985. dynamic1,
  986. dynamic2,
  987. blah: "goodbye {world}",
  988. onclick: |e| { println!("clicked") },
  989. }
  990. };
  991. let hot_reload = hot_reload_from_tokens(a, b).unwrap();
  992. let template = hot_reload.get(&0).unwrap();
  993. assert_eq!(
  994. template.component_values,
  995. &[
  996. HotReloadLiteral::Int(456),
  997. HotReloadLiteral::Float(789.456),
  998. HotReloadLiteral::Bool(false),
  999. HotReloadLiteral::Fmted(FmtedSegments::new(vec![
  1000. FmtSegment::Literal { value: "goodbye " },
  1001. FmtSegment::Dynamic { id: 1 }
  1002. ]))
  1003. ]
  1004. );
  1005. }
  1006. #[test]
  1007. fn component_modify_key() {
  1008. let a = quote! {
  1009. Component {
  1010. key: "{key}",
  1011. class: 123,
  1012. id: 456.789,
  1013. other: true,
  1014. dynamic1,
  1015. dynamic2,
  1016. blah1: "hello {world123}",
  1017. blah2: "hello {world}",
  1018. onclick: |e| { println!("clicked") },
  1019. }
  1020. };
  1021. // changing lit values
  1022. let b = quote! {
  1023. Component {
  1024. key: "{key}-{world}",
  1025. class: 456,
  1026. id: 789.456,
  1027. other: false,
  1028. dynamic1,
  1029. dynamic2,
  1030. blah1: "hello {world123}",
  1031. blah2: "hello {world}",
  1032. onclick: |e| { println!("clicked") },
  1033. }
  1034. };
  1035. let hot_reload = hot_reload_from_tokens(a, b).unwrap();
  1036. let template = hot_reload.get(&0).unwrap();
  1037. assert_eq!(
  1038. template.key,
  1039. Some(FmtedSegments::new(vec![
  1040. FmtSegment::Dynamic { id: 0 },
  1041. FmtSegment::Literal { value: "-" },
  1042. FmtSegment::Dynamic { id: 2 },
  1043. ]))
  1044. );
  1045. assert_eq!(
  1046. template.component_values,
  1047. &[
  1048. HotReloadLiteral::Int(456),
  1049. HotReloadLiteral::Float(789.456),
  1050. HotReloadLiteral::Bool(false),
  1051. HotReloadLiteral::Fmted(FmtedSegments::new(vec![
  1052. FmtSegment::Literal { value: "hello " },
  1053. FmtSegment::Dynamic { id: 1 }
  1054. ])),
  1055. HotReloadLiteral::Fmted(FmtedSegments::new(vec![
  1056. FmtSegment::Literal { value: "hello " },
  1057. FmtSegment::Dynamic { id: 2 }
  1058. ]))
  1059. ]
  1060. );
  1061. }
  1062. #[test]
  1063. fn duplicating_dynamic_nodes() {
  1064. let a = quote! {
  1065. div {
  1066. {some_expr}
  1067. }
  1068. };
  1069. // we can clone dynamic nodes to hot reload them
  1070. let b = quote! {
  1071. div {
  1072. {some_expr}
  1073. {some_expr}
  1074. }
  1075. };
  1076. let valid = can_hotreload(a, b);
  1077. assert!(valid);
  1078. }
  1079. #[test]
  1080. fn duplicating_dynamic_attributes() {
  1081. let a = quote! {
  1082. div {
  1083. width: value,
  1084. }
  1085. };
  1086. // we can clone dynamic nodes to hot reload them
  1087. let b = quote! {
  1088. div {
  1089. width: value,
  1090. height: value,
  1091. }
  1092. };
  1093. let valid = can_hotreload(a, b);
  1094. assert!(valid);
  1095. }
  1096. // We should be able to fill in empty nodes
  1097. #[test]
  1098. fn valid_fill_empty() {
  1099. let valid = can_hotreload(
  1100. quote! {},
  1101. quote! {
  1102. div { "x is 123" }
  1103. },
  1104. );
  1105. assert!(valid);
  1106. }
  1107. // We should be able to hot reload spreads
  1108. #[test]
  1109. fn valid_spread() {
  1110. let valid = can_hotreload(
  1111. quote! {
  1112. div {
  1113. ..spread
  1114. }
  1115. },
  1116. quote! {
  1117. div {
  1118. "hello world"
  1119. }
  1120. h1 {
  1121. ..spread
  1122. }
  1123. },
  1124. );
  1125. assert!(valid);
  1126. }