1
0

hotreload_pattern.rs 30 KB

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