lib.rs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. //! Parse the root tokens in the rsx!{} macro
  2. //! =========================================
  3. //!
  4. //! This parsing path emerges directly from the macro call, with `RsxRender` being the primary entrance into parsing.
  5. //! This feature must support:
  6. //! - [x] Optionally rendering if the `in XYZ` pattern is present
  7. //! - [x] Fragments as top-level element (through ambiguous)
  8. //! - [x] Components as top-level element (through ambiguous)
  9. //! - [x] Tags as top-level elements (through ambiguous)
  10. //! - [x] Good errors if parsing fails
  11. //!
  12. //! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
  13. #[macro_use]
  14. mod errors;
  15. mod component;
  16. mod element;
  17. mod hot_reloading_context;
  18. mod ifmt;
  19. mod node;
  20. use std::{collections::HashMap, hash::Hash};
  21. // Re-export the namespaces into each other
  22. pub use component::*;
  23. use dioxus_core::{Template, TemplateAttribute, TemplateNode};
  24. pub use element::*;
  25. use hot_reloading_context::Empty;
  26. pub use hot_reloading_context::HotReloadingContext;
  27. pub use ifmt::*;
  28. use internment::Intern;
  29. pub use node::*;
  30. // imports
  31. use proc_macro2::TokenStream as TokenStream2;
  32. use quote::{quote, ToTokens, TokenStreamExt};
  33. use syn::{
  34. parse::{Parse, ParseStream},
  35. Result, Token,
  36. };
  37. // interns a object into a static object, resusing the value if it already exists
  38. fn intern<T: Eq + Hash + Send + Sync + ?Sized + 'static>(s: impl Into<Intern<T>>) -> &'static T {
  39. s.into().as_ref()
  40. }
  41. /// Fundametnally, every CallBody is a template
  42. #[derive(Default, Debug)]
  43. pub struct CallBody<Ctx: HotReloadingContext = Empty> {
  44. pub roots: Vec<BodyNode>,
  45. // set this after
  46. pub inline_cx: bool,
  47. phantom: std::marker::PhantomData<Ctx>,
  48. }
  49. impl<Ctx: HotReloadingContext> CallBody<Ctx> {
  50. /// This will try to create a new template from the current body and the previous body. This will return None if the rsx has some dynamic part that has changed.
  51. /// This function intentionally leaks memory to create a static template.
  52. /// Keeping the template static allows us to simplify the core of dioxus and leaking memory in dev mode is less of an issue.
  53. /// the previous_location is the location of the previous template at the time the template was originally compiled.
  54. pub fn update_template(
  55. &self,
  56. template: Option<CallBody<Ctx>>,
  57. location: &'static str,
  58. ) -> Option<Template> {
  59. let mut renderer: TemplateRenderer<Ctx> = TemplateRenderer {
  60. roots: &self.roots,
  61. phantom: std::marker::PhantomData,
  62. };
  63. renderer.update_template(template, location)
  64. }
  65. }
  66. impl<Ctx: HotReloadingContext> Parse for CallBody<Ctx> {
  67. fn parse(input: ParseStream) -> Result<Self> {
  68. let mut roots = Vec::new();
  69. while !input.is_empty() {
  70. let node = input.parse::<BodyNode>()?;
  71. if input.peek(Token![,]) {
  72. let _ = input.parse::<Token![,]>();
  73. }
  74. roots.push(node);
  75. }
  76. Ok(Self {
  77. roots,
  78. inline_cx: false,
  79. phantom: std::marker::PhantomData,
  80. })
  81. }
  82. }
  83. /// Serialize the same way, regardless of flavor
  84. impl<Ctx: HotReloadingContext> ToTokens for CallBody<Ctx> {
  85. fn to_tokens(&self, out_tokens: &mut TokenStream2) {
  86. let body: TemplateRenderer<Ctx> = TemplateRenderer {
  87. roots: &self.roots,
  88. phantom: std::marker::PhantomData,
  89. };
  90. if self.inline_cx {
  91. out_tokens.append_all(quote! {
  92. Ok({
  93. let __cx = cx;
  94. #body
  95. })
  96. })
  97. } else {
  98. out_tokens.append_all(quote! {
  99. ::dioxus::core::LazyNodes::new( move | __cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode {
  100. #body
  101. })
  102. })
  103. }
  104. }
  105. }
  106. pub struct TemplateRenderer<'a, Ctx: HotReloadingContext = Empty> {
  107. pub roots: &'a [BodyNode],
  108. phantom: std::marker::PhantomData<Ctx>,
  109. }
  110. impl<'a, Ctx: HotReloadingContext> TemplateRenderer<'a, Ctx> {
  111. fn update_template(
  112. &mut self,
  113. previous_call: Option<CallBody<Ctx>>,
  114. location: &'static str,
  115. ) -> Option<Template<'static>> {
  116. let mut mapping = previous_call.map(|call| DynamicMapping::from(call.roots));
  117. let mut context: DynamicContext<Ctx> = DynamicContext::default();
  118. let mut roots = Vec::new();
  119. for (idx, root) in self.roots.iter().enumerate() {
  120. context.current_path.push(idx as u8);
  121. roots.push(context.update_node(root, &mut mapping)?);
  122. context.current_path.pop();
  123. }
  124. Some(Template {
  125. name: location,
  126. roots: intern(roots.as_slice()),
  127. node_paths: intern(
  128. context
  129. .node_paths
  130. .into_iter()
  131. .map(|path| intern(path.as_slice()))
  132. .collect::<Vec<_>>()
  133. .as_slice(),
  134. ),
  135. attr_paths: intern(
  136. context
  137. .attr_paths
  138. .into_iter()
  139. .map(|path| intern(path.as_slice()))
  140. .collect::<Vec<_>>()
  141. .as_slice(),
  142. ),
  143. })
  144. }
  145. }
  146. impl<'a, Ctx: HotReloadingContext> ToTokens for TemplateRenderer<'a, Ctx> {
  147. fn to_tokens(&self, out_tokens: &mut TokenStream2) {
  148. let mut context: DynamicContext<Ctx> = DynamicContext::default();
  149. let key = match self.roots.get(0) {
  150. Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(),
  151. Some(BodyNode::Component(comp)) if self.roots.len() == 1 => comp.key().cloned(),
  152. _ => None,
  153. };
  154. let key_tokens = match key {
  155. Some(tok) => quote! { Some( __cx.raw_text(#tok) ) },
  156. None => quote! { None },
  157. };
  158. let spndbg = format!("{:?}", self.roots[0].span());
  159. let root_col = spndbg[9..].split("..").next().unwrap();
  160. let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
  161. context.current_path.push(idx as u8);
  162. let out = context.render_static_node(root);
  163. context.current_path.pop();
  164. out
  165. });
  166. // Render and release the mutable borrow on context
  167. let num_roots = self.roots.len();
  168. let roots = quote! { #( #root_printer ),* };
  169. let node_printer = &context.dynamic_nodes;
  170. let dyn_attr_printer = &context.dynamic_attributes;
  171. let node_paths = context.node_paths.iter().map(|it| quote!(&[#(#it),*]));
  172. let attr_paths = context.attr_paths.iter().map(|it| quote!(&[#(#it),*]));
  173. out_tokens.append_all(quote! {
  174. static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
  175. name: concat!(
  176. file!(),
  177. ":",
  178. line!(),
  179. ":",
  180. column!(),
  181. ":",
  182. #root_col
  183. ),
  184. roots: &[ #roots ],
  185. node_paths: &[ #(#node_paths),* ],
  186. attr_paths: &[ #(#attr_paths),* ],
  187. };
  188. ::dioxus::core::VNode {
  189. parent: None,
  190. key: #key_tokens,
  191. template: TEMPLATE,
  192. root_ids: std::cell::Cell::from_mut( __cx.bump().alloc([None; #num_roots]) as &mut _).as_slice_of_cells(),
  193. // root_ids: std::cell::Cell::from_mut( __cx.bump().alloc([None; #num_roots]) as &mut [::dioxus::core::ElementId]).as_slice_of_cells(),
  194. dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
  195. dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
  196. }
  197. });
  198. }
  199. }
  200. #[derive(Debug, Default)]
  201. struct DynamicMapping {
  202. attribute_to_idx: HashMap<ElementAttr, Vec<usize>>,
  203. last_attribute_idx: usize,
  204. node_to_idx: HashMap<BodyNode, Vec<usize>>,
  205. last_element_idx: usize,
  206. }
  207. impl DynamicMapping {
  208. fn from(nodes: Vec<BodyNode>) -> Self {
  209. let mut new = Self::default();
  210. for node in nodes {
  211. new.add_node(node);
  212. }
  213. println!("{:#?}", new);
  214. new
  215. }
  216. fn get_attribute_idx(&mut self, attr: &ElementAttr) -> Option<usize> {
  217. self.attribute_to_idx
  218. .get_mut(attr)
  219. .and_then(|idxs| idxs.pop())
  220. }
  221. fn get_node_idx(&mut self, node: &BodyNode) -> Option<usize> {
  222. self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop())
  223. }
  224. fn insert_attribute(&mut self, attr: ElementAttr) -> usize {
  225. let idx = self.last_attribute_idx;
  226. self.last_attribute_idx += 1;
  227. self.attribute_to_idx
  228. .entry(attr)
  229. .or_insert_with(Vec::new)
  230. .push(idx);
  231. idx
  232. }
  233. fn insert_node(&mut self, node: BodyNode) -> usize {
  234. let idx = self.last_element_idx;
  235. self.last_element_idx += 1;
  236. self.node_to_idx
  237. .entry(node)
  238. .or_insert_with(Vec::new)
  239. .push(idx);
  240. idx
  241. }
  242. fn add_node(&mut self, node: BodyNode) {
  243. match node {
  244. BodyNode::Element(el) => {
  245. for attr in el.attributes {
  246. match &attr.attr {
  247. ElementAttr::CustomAttrText { value, .. }
  248. | ElementAttr::AttrText { value, .. }
  249. if value.is_static() => {}
  250. ElementAttr::AttrExpression { .. }
  251. | ElementAttr::AttrText { .. }
  252. | ElementAttr::CustomAttrText { .. }
  253. | ElementAttr::CustomAttrExpression { .. }
  254. | ElementAttr::EventTokens { .. } => {
  255. self.insert_attribute(attr.attr);
  256. }
  257. }
  258. }
  259. for child in el.children {
  260. self.add_node(child);
  261. }
  262. }
  263. BodyNode::Text(text) if text.is_static() => {}
  264. BodyNode::RawExpr(_)
  265. | BodyNode::Text(_)
  266. | BodyNode::ForLoop(_)
  267. | BodyNode::IfChain(_)
  268. | BodyNode::Component(_) => {
  269. self.insert_node(node);
  270. }
  271. }
  272. }
  273. }
  274. // As we create the dynamic nodes, we want to keep track of them in a linear fashion
  275. // We'll use the size of the vecs to determine the index of the dynamic node in the final output
  276. pub struct DynamicContext<'a, Ctx: HotReloadingContext> {
  277. dynamic_nodes: Vec<&'a BodyNode>,
  278. dynamic_attributes: Vec<&'a ElementAttrNamed>,
  279. current_path: Vec<u8>,
  280. node_paths: Vec<Vec<u8>>,
  281. attr_paths: Vec<Vec<u8>>,
  282. phantom: std::marker::PhantomData<Ctx>,
  283. }
  284. impl<'a, Ctx: HotReloadingContext> Default for DynamicContext<'a, Ctx> {
  285. fn default() -> Self {
  286. Self {
  287. dynamic_nodes: Vec::new(),
  288. dynamic_attributes: Vec::new(),
  289. current_path: Vec::new(),
  290. node_paths: Vec::new(),
  291. attr_paths: Vec::new(),
  292. phantom: std::marker::PhantomData,
  293. }
  294. }
  295. }
  296. impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> {
  297. fn update_node(
  298. &mut self,
  299. root: &'a BodyNode,
  300. mapping: &mut Option<DynamicMapping>,
  301. ) -> Option<TemplateNode<'static>> {
  302. match root {
  303. BodyNode::Element(el) => {
  304. let element_name_rust = el.name.to_string();
  305. let mut static_attrs = Vec::new();
  306. for attr in &el.attributes {
  307. match &attr.attr {
  308. ElementAttr::AttrText { name, value } if value.is_static() => {
  309. let value = value.source.as_ref().unwrap();
  310. let attribute_name_rust = name.to_string();
  311. let (name, namespace) =
  312. Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
  313. .unwrap_or((intern(attribute_name_rust.as_str()), None));
  314. static_attrs.push(TemplateAttribute::Static {
  315. name,
  316. namespace,
  317. value: intern(value.value().as_str()),
  318. })
  319. }
  320. ElementAttr::CustomAttrText { name, value } if value.is_static() => {
  321. let value = value.source.as_ref().unwrap();
  322. static_attrs.push(TemplateAttribute::Static {
  323. name: intern(name.value().as_str()),
  324. namespace: None,
  325. value: intern(value.value().as_str()),
  326. })
  327. }
  328. ElementAttr::AttrExpression { .. }
  329. | ElementAttr::AttrText { .. }
  330. | ElementAttr::CustomAttrText { .. }
  331. | ElementAttr::CustomAttrExpression { .. }
  332. | ElementAttr::EventTokens { .. } => {
  333. let ct = match mapping {
  334. Some(mapping) => mapping.get_attribute_idx(&attr.attr)?,
  335. None => self.dynamic_attributes.len(),
  336. };
  337. self.dynamic_attributes.push(attr);
  338. self.attr_paths.push(self.current_path.clone());
  339. static_attrs.push(TemplateAttribute::Dynamic { id: ct })
  340. }
  341. }
  342. }
  343. let mut children = Vec::new();
  344. for (idx, root) in el.children.iter().enumerate() {
  345. self.current_path.push(idx as u8);
  346. children.push(self.update_node(root, mapping)?);
  347. self.current_path.pop();
  348. }
  349. let (tag, namespace) = Ctx::map_element(&element_name_rust)
  350. .unwrap_or((intern(element_name_rust.as_str()), None));
  351. Some(TemplateNode::Element {
  352. tag,
  353. namespace,
  354. attrs: intern(static_attrs.into_boxed_slice()),
  355. children: intern(children.as_slice()),
  356. })
  357. }
  358. BodyNode::Text(text) if text.is_static() => {
  359. let text = text.source.as_ref().unwrap();
  360. Some(TemplateNode::Text {
  361. text: intern(text.value().as_str()),
  362. })
  363. }
  364. BodyNode::RawExpr(_)
  365. | BodyNode::Text(_)
  366. | BodyNode::ForLoop(_)
  367. | BodyNode::IfChain(_)
  368. | BodyNode::Component(_) => {
  369. let ct = match mapping {
  370. Some(mapping) => mapping.get_node_idx(root)?,
  371. None => self.dynamic_nodes.len(),
  372. };
  373. self.dynamic_nodes.push(root);
  374. self.node_paths.push(self.current_path.clone());
  375. Some(match root {
  376. BodyNode::Text(_) => TemplateNode::DynamicText { id: ct },
  377. _ => TemplateNode::Dynamic { id: ct },
  378. })
  379. }
  380. }
  381. }
  382. fn render_static_node(&mut self, root: &'a BodyNode) -> TokenStream2 {
  383. match root {
  384. BodyNode::Element(el) => {
  385. let el_name = &el.name;
  386. let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
  387. ElementAttr::AttrText { name, value } if value.is_static() => {
  388. let value = value.to_static().unwrap();
  389. quote! {
  390. ::dioxus::core::TemplateAttribute::Static {
  391. name: dioxus_elements::#el_name::#name.0,
  392. namespace: dioxus_elements::#el_name::#name.1,
  393. value: #value,
  394. // todo: we don't diff these so we never apply the volatile flag
  395. // volatile: dioxus_elements::#el_name::#name.2,
  396. }
  397. }
  398. }
  399. ElementAttr::CustomAttrText { name, value } if value.is_static() => {
  400. let value = value.to_static().unwrap();
  401. quote! {
  402. ::dioxus::core::TemplateAttribute::Static {
  403. name: #name,
  404. namespace: None,
  405. value: #value,
  406. // todo: we don't diff these so we never apply the volatile flag
  407. // volatile: dioxus_elements::#el_name::#name.2,
  408. }
  409. }
  410. }
  411. ElementAttr::AttrExpression { .. }
  412. | ElementAttr::AttrText { .. }
  413. | ElementAttr::CustomAttrText { .. }
  414. | ElementAttr::CustomAttrExpression { .. }
  415. | ElementAttr::EventTokens { .. } => {
  416. let ct = self.dynamic_attributes.len();
  417. self.dynamic_attributes.push(attr);
  418. self.attr_paths.push(self.current_path.clone());
  419. quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } }
  420. }
  421. });
  422. let attrs = quote! { #(#static_attrs),*};
  423. let children = el.children.iter().enumerate().map(|(idx, root)| {
  424. self.current_path.push(idx as u8);
  425. let out = self.render_static_node(root);
  426. self.current_path.pop();
  427. out
  428. });
  429. let _opt = el.children.len() == 1;
  430. let children = quote! { #(#children),* };
  431. quote! {
  432. ::dioxus::core::TemplateNode::Element {
  433. tag: dioxus_elements::#el_name::TAG_NAME,
  434. namespace: dioxus_elements::#el_name::NAME_SPACE,
  435. attrs: &[ #attrs ],
  436. children: &[ #children ],
  437. }
  438. }
  439. }
  440. BodyNode::Text(text) if text.is_static() => {
  441. let text = text.to_static().unwrap();
  442. quote! { ::dioxus::core::TemplateNode::Text{ text: #text } }
  443. }
  444. BodyNode::RawExpr(_)
  445. | BodyNode::Text(_)
  446. | BodyNode::ForLoop(_)
  447. | BodyNode::IfChain(_)
  448. | BodyNode::Component(_) => {
  449. let ct = self.dynamic_nodes.len();
  450. self.dynamic_nodes.push(root);
  451. self.node_paths.push(self.current_path.clone());
  452. match root {
  453. BodyNode::Text(_) => {
  454. quote! { ::dioxus::core::TemplateNode::DynamicText { id: #ct } }
  455. }
  456. _ => quote! { ::dioxus::core::TemplateNode::Dynamic { id: #ct } },
  457. }
  458. }
  459. }
  460. }
  461. }
  462. #[test]
  463. fn create_template() {
  464. let input = quote! {
  465. svg {
  466. width: 100,
  467. height: "100px",
  468. "width2": 100,
  469. "height2": "100px",
  470. p {
  471. "hello world"
  472. }
  473. (0..10).map(|i| rsx!{"{i}"})
  474. }
  475. };
  476. struct Mock;
  477. impl HotReloadingContext for Mock {
  478. fn map_attribute(
  479. element_name_rust: &str,
  480. attribute_name_rust: &str,
  481. ) -> Option<(&'static str, Option<&'static str>)> {
  482. match element_name_rust {
  483. "svg" => match attribute_name_rust {
  484. "width" => Some(("width", Some("style"))),
  485. "height" => Some(("height", Some("style"))),
  486. _ => None,
  487. },
  488. _ => None,
  489. }
  490. }
  491. fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
  492. match element_name_rust {
  493. "svg" => Some(("svg", Some("svg"))),
  494. _ => None,
  495. }
  496. }
  497. }
  498. let call_body: CallBody<Mock> = syn::parse2(input).unwrap();
  499. let template = call_body.update_template(None, "testing").unwrap();
  500. dbg!(template);
  501. assert_eq!(
  502. template,
  503. Template {
  504. name: "testing",
  505. roots: &[TemplateNode::Element {
  506. tag: "svg",
  507. namespace: Some("svg"),
  508. attrs: &[
  509. TemplateAttribute::Dynamic { id: 0 },
  510. TemplateAttribute::Static {
  511. name: "height",
  512. namespace: Some("style"),
  513. value: "100px",
  514. },
  515. TemplateAttribute::Dynamic { id: 1 },
  516. TemplateAttribute::Static {
  517. name: "height2",
  518. namespace: None,
  519. value: "100px",
  520. },
  521. ],
  522. children: &[
  523. TemplateNode::Element {
  524. tag: "p",
  525. namespace: None,
  526. attrs: &[],
  527. children: &[TemplateNode::Text {
  528. text: "hello world",
  529. }],
  530. },
  531. TemplateNode::Dynamic { id: 0 }
  532. ],
  533. }],
  534. node_paths: &[&[0, 1,],],
  535. attr_paths: &[&[0,], &[0,],],
  536. },
  537. )
  538. }
  539. #[test]
  540. fn diff_template() {
  541. let input = quote! {
  542. svg {
  543. width: 100,
  544. height: "100px",
  545. "width2": 100,
  546. "height2": "100px",
  547. p {
  548. "hello world"
  549. }
  550. (0..10).map(|i| rsx!{"{i}"}),
  551. (0..10).map(|i| rsx!{"{i}"}),
  552. (0..11).map(|i| rsx!{"{i}"}),
  553. }
  554. };
  555. #[derive(Debug)]
  556. struct Mock;
  557. impl HotReloadingContext for Mock {
  558. fn map_attribute(
  559. element_name_rust: &str,
  560. attribute_name_rust: &str,
  561. ) -> Option<(&'static str, Option<&'static str>)> {
  562. match element_name_rust {
  563. "svg" => match attribute_name_rust {
  564. "width" => Some(("width", Some("style"))),
  565. "height" => Some(("height", Some("style"))),
  566. _ => None,
  567. },
  568. _ => None,
  569. }
  570. }
  571. fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
  572. match element_name_rust {
  573. "svg" => Some(("svg", Some("svg"))),
  574. _ => None,
  575. }
  576. }
  577. }
  578. let call_body1: CallBody<Mock> = syn::parse2(input).unwrap();
  579. let template = call_body1.update_template(None, "testing").unwrap();
  580. dbg!(template);
  581. // scrambling the attributes should not cause a full rebuild
  582. let input = quote! {
  583. div {
  584. "width2": 100,
  585. height: "100px",
  586. "height2": "100px",
  587. width: 100,
  588. (0..11).map(|i| rsx!{"{i}"}),
  589. (0..10).map(|i| rsx!{"{i}"}),
  590. (0..10).map(|i| rsx!{"{i}"}),
  591. p {
  592. "hello world"
  593. }
  594. }
  595. };
  596. let call_body2: CallBody<Mock> = syn::parse2(input).unwrap();
  597. let template = call_body2
  598. .update_template(Some(call_body1), "testing")
  599. .unwrap();
  600. assert_eq!(
  601. template,
  602. Template {
  603. name: "testing",
  604. roots: &[TemplateNode::Element {
  605. tag: "div",
  606. namespace: None,
  607. attrs: &[
  608. TemplateAttribute::Dynamic { id: 1 },
  609. TemplateAttribute::Static {
  610. name: "height",
  611. namespace: None,
  612. value: "100px",
  613. },
  614. TemplateAttribute::Static {
  615. name: "height2",
  616. namespace: None,
  617. value: "100px",
  618. },
  619. TemplateAttribute::Dynamic { id: 0 },
  620. ],
  621. children: &[
  622. TemplateNode::Dynamic { id: 2 },
  623. TemplateNode::Dynamic { id: 1 },
  624. TemplateNode::Dynamic { id: 0 },
  625. TemplateNode::Element {
  626. tag: "p",
  627. namespace: None,
  628. attrs: &[],
  629. children: &[TemplateNode::Text {
  630. text: "hello world",
  631. }],
  632. },
  633. ],
  634. }],
  635. node_paths: &[&[0, 0], &[0, 1], &[0, 2]],
  636. attr_paths: &[&[0], &[0]]
  637. },
  638. )
  639. }