hotreload_pattern.rs 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289
  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 invalid_empty_rsx() {
  412. let old_template = quote! {
  413. div {
  414. for item in vec![1, 2, 3, 4] {
  415. div { "asasddasdasd" }
  416. div { "123" }
  417. }
  418. for item in vec![4, 5, 6] {
  419. span { "asasddasdasd" }
  420. span { "123" }
  421. }
  422. }
  423. };
  424. // empty out the whole rsx block
  425. let new_template = quote! {};
  426. let location = "file:line:col:0";
  427. let old_template: CallBody = syn::parse2(old_template).unwrap();
  428. let new_template: CallBody = syn::parse2(new_template).unwrap();
  429. assert!(hotreload_callbody::<Mock>(&old_template, &new_template).is_none());
  430. }
  431. #[test]
  432. fn attributes_reload() {
  433. let old = quote! {
  434. div {
  435. class: "{class}",
  436. id: "{id}",
  437. name: "name",
  438. }
  439. };
  440. // Same order, just different contents
  441. let new_valid_internal = quote! {
  442. div {
  443. id: "{id}",
  444. name: "name",
  445. class: "{class}"
  446. }
  447. };
  448. let templates = hot_reload_from_tokens(old, new_valid_internal).unwrap();
  449. dbg!(templates);
  450. }
  451. #[test]
  452. fn template_generates() {
  453. let old = quote! {
  454. svg {
  455. width: 100,
  456. height: "100px",
  457. "width2": 100,
  458. "height2": "100px",
  459. p { "hello world" }
  460. {(0..10).map(|i| rsx! {"{i}"})}
  461. }
  462. div {
  463. width: 120,
  464. div {
  465. height: "100px",
  466. "width2": 130,
  467. "height2": "100px",
  468. for i in 0..10 {
  469. div {
  470. "asdasd"
  471. }
  472. }
  473. }
  474. }
  475. };
  476. let old: CallBody = syn::parse2(old).unwrap();
  477. let template = callbody_to_template::<Mock>(&old, "file:line:col:0");
  478. }
  479. #[test]
  480. fn diffs_complex() {
  481. #[allow(unused, non_snake_case)]
  482. fn Comp() -> dioxus_core::Element {
  483. VNode::empty()
  484. }
  485. let old = quote! {
  486. svg {
  487. width: 100,
  488. height: "100px",
  489. "width2": 100,
  490. "height2": "100px",
  491. p { "hello world" }
  492. {(0..10).map(|i| rsx! {"{i}"})},
  493. {(0..10).map(|i| rsx! {"{i}"})},
  494. {(0..11).map(|i| rsx! {"{i}"})},
  495. Comp {}
  496. }
  497. };
  498. // scrambling the attributes should not cause a full rebuild
  499. let new = quote! {
  500. div {
  501. width: 100,
  502. height: "100px",
  503. "width2": 100,
  504. "height2": "100px",
  505. p { "hello world" }
  506. Comp {}
  507. {(0..10).map(|i| rsx! {"{i}"})},
  508. {(0..10).map(|i| rsx! {"{i}"})},
  509. {(0..11).map(|i| rsx! {"{i}"})},
  510. }
  511. };
  512. let old: CallBody = syn::parse2(old).unwrap();
  513. let new: CallBody = syn::parse2(new).unwrap();
  514. let templates = hotreload_callbody::<Mock>(&old, &new).unwrap();
  515. }
  516. #[test]
  517. fn remove_node() {
  518. let valid = hot_reload_from_tokens(
  519. quote! {
  520. svg {
  521. Comp {}
  522. {(0..10).map(|i| rsx! {"{i}"})},
  523. }
  524. },
  525. quote! {
  526. div {
  527. {(0..10).map(|i| rsx! {"{i}"})},
  528. }
  529. },
  530. )
  531. .unwrap();
  532. dbg!(valid);
  533. }
  534. #[test]
  535. fn if_chains() {
  536. let valid = hot_reload_from_tokens(
  537. quote! {
  538. if cond {
  539. "foo"
  540. }
  541. },
  542. quote! {
  543. if cond {
  544. "baz"
  545. }
  546. },
  547. )
  548. .unwrap();
  549. let very_complex_chain = hot_reload_from_tokens(
  550. quote! {
  551. if cond {
  552. if second_cond {
  553. "foo"
  554. }
  555. } else if othercond {
  556. "bar"
  557. } else {
  558. "baz"
  559. }
  560. },
  561. quote! {
  562. if cond {
  563. if second_cond {
  564. span { "asasddasdasd 789" }
  565. }
  566. } else if othercond {
  567. span { "asasddasdasd 123" }
  568. } else {
  569. span { "asasddasdas 456" }
  570. }
  571. },
  572. )
  573. .unwrap();
  574. dbg!(very_complex_chain);
  575. }
  576. #[test]
  577. fn component_bodies() {
  578. let valid = can_hotreload(
  579. quote! {
  580. Comp {
  581. "foo"
  582. }
  583. },
  584. quote! {
  585. Comp {
  586. "baz"
  587. }
  588. },
  589. );
  590. assert!(valid);
  591. }
  592. // We currently don't track aliasing which means we can't allow dynamic nodes/formatted segments to be moved between scopes
  593. #[test]
  594. fn moving_between_scopes() {
  595. let valid = can_hotreload(
  596. quote! {
  597. for x in 0..10 {
  598. for y in 0..10 {
  599. div { "x is {x}" }
  600. }
  601. }
  602. },
  603. quote! {
  604. for x in 0..10 {
  605. div { "x is {x}" }
  606. }
  607. },
  608. );
  609. assert!(!valid);
  610. }
  611. /// Everything reloads!
  612. #[test]
  613. fn kitch_sink_of_reloadability() {
  614. let valid = hot_reload_from_tokens(
  615. quote! {
  616. div {
  617. for i in 0..10 {
  618. div { "123" }
  619. Comp {
  620. "foo"
  621. }
  622. if cond {
  623. "foo"
  624. }
  625. }
  626. }
  627. },
  628. quote! {
  629. div {
  630. "hi!"
  631. for i in 0..10 {
  632. div { "456" }
  633. Comp { "bar" }
  634. if cond {
  635. "baz"
  636. }
  637. }
  638. }
  639. },
  640. )
  641. .unwrap();
  642. dbg!(valid);
  643. }
  644. /// Moving nodes inbetween multiple rsx! calls currently doesn't work
  645. /// Sad. Needs changes to core to work, and is technically flawed?
  646. #[test]
  647. fn entire_kitchen_sink() {
  648. let valid = hot_reload_from_tokens(
  649. quote! {
  650. div {
  651. for i in 0..10 {
  652. div { "123" }
  653. }
  654. Comp {
  655. "foo"
  656. }
  657. if cond {
  658. "foo"
  659. }
  660. }
  661. },
  662. quote! {
  663. div {
  664. "hi!"
  665. Comp {
  666. for i in 0..10 {
  667. div { "456" }
  668. }
  669. "bar"
  670. if cond {
  671. "baz"
  672. }
  673. }
  674. }
  675. },
  676. );
  677. assert!(valid.is_none());
  678. }
  679. #[test]
  680. fn tokenstreams_and_locations() {
  681. let valid = hot_reload_from_tokens(
  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 { "its cool?" }
  698. } else {
  699. div { "not nice !" }
  700. }
  701. }
  702. },
  703. quote! {
  704. div { "hhi" }
  705. div {
  706. {rsx! { "hi again!" }},
  707. for i in 0..2 {
  708. "first"
  709. div { "hi {i}" }
  710. }
  711. for i in 0..3 {
  712. "Second"
  713. div { "hi {i}" }
  714. }
  715. if false {
  716. div { "hi again?" }
  717. } else if true {
  718. div { "cool?" }
  719. } else {
  720. div { "nice !" }
  721. }
  722. }
  723. },
  724. );
  725. dbg!(valid);
  726. }
  727. #[test]
  728. fn ide_testcase() {
  729. let valid = hot_reload_from_tokens(
  730. 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. quote! {
  739. div {
  740. div { "hi!!!123 in!stant relo123a1123dasasdasdasdasd" }
  741. for x in 0..5 {
  742. h3 { "For loop contents" }
  743. }
  744. }
  745. },
  746. );
  747. dbg!(valid);
  748. }
  749. #[test]
  750. fn assigns_ids() {
  751. let toks = quote! {
  752. div {
  753. div { "hi!!!123 in!stant relo123a1123dasasdasdasdasd" }
  754. for x in 0..5 {
  755. h3 { "For loop contents" }
  756. }
  757. }
  758. };
  759. let parsed = syn::parse2::<CallBody>(toks).unwrap();
  760. let node = parsed.body.get_dyn_node(&[0, 1]);
  761. dbg!(node);
  762. }
  763. #[test]
  764. fn simple_start() {
  765. let valid = can_hotreload(
  766. //
  767. quote! {
  768. div {
  769. class: "Some {one}",
  770. id: "Something {two}",
  771. "One"
  772. }
  773. },
  774. quote! {
  775. div {
  776. id: "Something {two}",
  777. class: "Some {one}",
  778. "One"
  779. }
  780. },
  781. );
  782. assert!(valid);
  783. }
  784. #[test]
  785. fn complex_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. class: "Some {one}",
  797. id: "Something else {two}",
  798. "One"
  799. }
  800. },
  801. );
  802. assert!(valid);
  803. }
  804. #[test]
  805. fn attribute_cases() {
  806. let valid = can_hotreload(
  807. quote! {
  808. div {
  809. class: "Some {one}",
  810. id: "Something {two}",
  811. "One"
  812. }
  813. },
  814. quote! {
  815. div {
  816. id: "Something {two}",
  817. "One"
  818. }
  819. },
  820. );
  821. assert!(valid);
  822. let valid = can_hotreload(
  823. //
  824. quote! { div { class: 123 } },
  825. quote! { div { class: 456 } },
  826. );
  827. assert!(valid);
  828. let valid = can_hotreload(
  829. //
  830. quote! { div { class: 123.0 } },
  831. quote! { div { class: 456.0 } },
  832. );
  833. assert!(valid);
  834. let valid = can_hotreload(
  835. //
  836. quote! { div { class: "asd {123}", } },
  837. quote! { div { class: "def", } },
  838. );
  839. assert!(valid);
  840. }
  841. #[test]
  842. fn text_node_cases() {
  843. let valid = can_hotreload(
  844. //
  845. quote! { div { "hello {world}" } },
  846. quote! { div { "world {world}" } },
  847. );
  848. assert!(valid);
  849. let valid = can_hotreload(
  850. //
  851. quote! { div { "hello {world}" } },
  852. quote! { div { "world" } },
  853. );
  854. assert!(valid);
  855. let valid = can_hotreload(
  856. //
  857. quote! { div { "hello {world}" } },
  858. quote! { div { "world {world} {world}" } },
  859. );
  860. assert!(valid);
  861. let valid = can_hotreload(
  862. //
  863. quote! { div { "hello" } },
  864. quote! { div { "world {world}" } },
  865. );
  866. assert!(!valid);
  867. }
  868. #[test]
  869. fn simple_carry() {
  870. let a = quote! {
  871. // start with
  872. "thing {abc} {def}" // 1, 1, 1
  873. "thing {def}" // 1, 0, 1
  874. "other {hij}" // 1, 1, 1
  875. };
  876. let b = quote! {
  877. // end with
  878. "thing {def}"
  879. "thing {abc}"
  880. "thing {hij}"
  881. };
  882. let valid = can_hotreload(a, b);
  883. assert!(valid);
  884. }
  885. #[test]
  886. fn complex_carry_text() {
  887. let a = quote! {
  888. // start with
  889. "thing {abc} {def}" // 1, 1, 1
  890. "thing {abc}" // 1, 0, 1
  891. "other {abc} {def} {hij}" // 1, 1, 1
  892. };
  893. let b = quote! {
  894. // end with
  895. "thing {abc}"
  896. "thing {hij}"
  897. };
  898. let valid = can_hotreload(a, b);
  899. assert!(valid);
  900. }
  901. #[test]
  902. fn complex_carry() {
  903. let a = quote! {
  904. Component {
  905. class: "thing {abc}",
  906. other: "other {abc} {def}",
  907. }
  908. Component {
  909. class: "thing {abc}",
  910. other: "other",
  911. }
  912. };
  913. let b = quote! {
  914. // how about shuffling components, for, if, etc
  915. Component {
  916. class: "thing {abc}",
  917. other: "other {abc} {def}",
  918. }
  919. Component {
  920. class: "thing",
  921. other: "other",
  922. }
  923. };
  924. let valid = can_hotreload(a, b);
  925. assert!(valid);
  926. }
  927. #[test]
  928. fn component_with_lits() {
  929. let a = quote! {
  930. Component {
  931. class: 123,
  932. id: 456.789,
  933. other: true,
  934. blah: "hello {world}",
  935. }
  936. };
  937. // changing lit values
  938. let b = quote! {
  939. Component {
  940. class: 456,
  941. id: 789.456,
  942. other: false,
  943. blah: "goodbye {world}",
  944. }
  945. };
  946. let valid = can_hotreload(a, b);
  947. assert!(valid);
  948. }
  949. #[test]
  950. fn component_with_handlers() {
  951. let a = quote! {
  952. Component {
  953. class: 123,
  954. id: 456.789,
  955. other: true,
  956. blah: "hello {world}",
  957. onclick: |e| { println!("clicked") },
  958. }
  959. };
  960. // changing lit values
  961. let b = quote! {
  962. Component {
  963. class: 456,
  964. id: 789.456,
  965. other: false,
  966. blah: "goodbye {world}",
  967. onclick: |e| { println!("clicked") },
  968. }
  969. };
  970. let hot_reload = hot_reload_from_tokens(a, b).unwrap();
  971. let template = hot_reload.get(&0).unwrap();
  972. assert_eq!(
  973. template.component_values,
  974. &[
  975. HotReloadLiteral::Int(456),
  976. HotReloadLiteral::Float(789.456),
  977. HotReloadLiteral::Bool(false),
  978. HotReloadLiteral::Fmted(FmtedSegments::new(vec![
  979. FmtSegment::Literal { value: "goodbye " },
  980. FmtSegment::Dynamic { id: 0 }
  981. ])),
  982. ]
  983. );
  984. }
  985. #[test]
  986. fn component_remove_key() {
  987. let a = quote! {
  988. Component {
  989. key: "{key}",
  990. class: 123,
  991. id: 456.789,
  992. other: true,
  993. dynamic1,
  994. dynamic2,
  995. blah: "hello {world}",
  996. onclick: |e| { println!("clicked") },
  997. }
  998. };
  999. // changing lit values
  1000. let b = quote! {
  1001. Component {
  1002. class: 456,
  1003. id: 789.456,
  1004. other: false,
  1005. dynamic1,
  1006. dynamic2,
  1007. blah: "goodbye {world}",
  1008. onclick: |e| { println!("clicked") },
  1009. }
  1010. };
  1011. let hot_reload = hot_reload_from_tokens(a, b).unwrap();
  1012. let template = hot_reload.get(&0).unwrap();
  1013. assert_eq!(
  1014. template.component_values,
  1015. &[
  1016. HotReloadLiteral::Int(456),
  1017. HotReloadLiteral::Float(789.456),
  1018. HotReloadLiteral::Bool(false),
  1019. HotReloadLiteral::Fmted(FmtedSegments::new(vec![
  1020. FmtSegment::Literal { value: "goodbye " },
  1021. FmtSegment::Dynamic { id: 1 }
  1022. ]))
  1023. ]
  1024. );
  1025. }
  1026. #[test]
  1027. fn component_modify_key() {
  1028. let a = quote! {
  1029. Component {
  1030. key: "{key}",
  1031. class: 123,
  1032. id: 456.789,
  1033. other: true,
  1034. dynamic1,
  1035. dynamic2,
  1036. blah1: "hello {world123}",
  1037. blah2: "hello {world}",
  1038. onclick: |e| { println!("clicked") },
  1039. }
  1040. };
  1041. // changing lit values
  1042. let b = quote! {
  1043. Component {
  1044. key: "{key}-{world}",
  1045. class: 456,
  1046. id: 789.456,
  1047. other: false,
  1048. dynamic1,
  1049. dynamic2,
  1050. blah1: "hello {world123}",
  1051. blah2: "hello {world}",
  1052. onclick: |e| { println!("clicked") },
  1053. }
  1054. };
  1055. let hot_reload = hot_reload_from_tokens(a, b).unwrap();
  1056. let template = hot_reload.get(&0).unwrap();
  1057. assert_eq!(
  1058. template.key,
  1059. Some(FmtedSegments::new(vec![
  1060. FmtSegment::Dynamic { id: 0 },
  1061. FmtSegment::Literal { value: "-" },
  1062. FmtSegment::Dynamic { id: 2 },
  1063. ]))
  1064. );
  1065. assert_eq!(
  1066. template.component_values,
  1067. &[
  1068. HotReloadLiteral::Int(456),
  1069. HotReloadLiteral::Float(789.456),
  1070. HotReloadLiteral::Bool(false),
  1071. HotReloadLiteral::Fmted(FmtedSegments::new(vec![
  1072. FmtSegment::Literal { value: "hello " },
  1073. FmtSegment::Dynamic { id: 1 }
  1074. ])),
  1075. HotReloadLiteral::Fmted(FmtedSegments::new(vec![
  1076. FmtSegment::Literal { value: "hello " },
  1077. FmtSegment::Dynamic { id: 2 }
  1078. ]))
  1079. ]
  1080. );
  1081. }
  1082. #[test]
  1083. fn duplicating_dynamic_nodes() {
  1084. let a = quote! {
  1085. div {
  1086. {some_expr}
  1087. }
  1088. };
  1089. // we can clone dynamic nodes to hot reload them
  1090. let b = quote! {
  1091. div {
  1092. {some_expr}
  1093. {some_expr}
  1094. }
  1095. };
  1096. let valid = can_hotreload(a, b);
  1097. assert!(valid);
  1098. }
  1099. #[test]
  1100. fn duplicating_dynamic_attributes() {
  1101. let a = quote! {
  1102. div {
  1103. width: value,
  1104. }
  1105. };
  1106. // we can clone dynamic nodes to hot reload them
  1107. let b = quote! {
  1108. div {
  1109. width: value,
  1110. height: value,
  1111. }
  1112. };
  1113. let valid = can_hotreload(a, b);
  1114. assert!(valid);
  1115. }
  1116. // We should be able to fill in empty nodes
  1117. #[test]
  1118. fn valid_fill_empty() {
  1119. let valid = can_hotreload(
  1120. quote! {},
  1121. quote! {
  1122. div { "x is 123" }
  1123. },
  1124. );
  1125. assert!(valid);
  1126. }
  1127. // We should be able to hot reload spreads
  1128. #[test]
  1129. fn valid_spread() {
  1130. let valid = can_hotreload(
  1131. quote! {
  1132. div {
  1133. ..spread
  1134. }
  1135. },
  1136. quote! {
  1137. div {
  1138. "hello world"
  1139. }
  1140. h1 {
  1141. ..spread
  1142. }
  1143. },
  1144. );
  1145. assert!(valid);
  1146. }