1use dioxus_const_vec::ConstVec;
2
3use super::anchor::ROOT_PARENT_OP_INDEX;
4use super::{Template, TemplateAnchor, TemplatePath, TemplateRawTree, TemplateSlotPath};
5use crate::op::TemplateOp;
6
7pub const TEMPLATE_STORAGE_MAX_CAP: usize = TemplateOp::MAX_CAP;
9
10pub const TEMPLATE_STORAGE_OPS_CAP: usize = 128;
12
13pub const TEMPLATE_STORAGE_STRING_CAP: usize = 128;
15
16pub const TEMPLATE_STORAGE_DYNAMIC_CAP: usize = 16;
18
19const TEMPLATE_PATH_STACK_CAP: usize = 128;
28
29#[derive(Clone, Copy)]
31pub struct TemplateStorage<
32 const OPS_CAP: usize = TEMPLATE_STORAGE_OPS_CAP,
33 const STRING_CAP: usize = TEMPLATE_STORAGE_STRING_CAP,
34 const DYNAMIC_CAP: usize = TEMPLATE_STORAGE_DYNAMIC_CAP,
35> {
36 ops: ConstVec<TemplateOp, OPS_CAP>,
37 strings: ConstVec<&'static str, STRING_CAP>,
38 anchors: ConstVec<TemplateAnchor, DYNAMIC_CAP>,
39}
40
41impl<const OPS_CAP: usize, const STRING_CAP: usize, const DYNAMIC_CAP: usize>
42 TemplateStorage<OPS_CAP, STRING_CAP, DYNAMIC_CAP>
43{
44 pub const fn build_from_tree(tree: &'static TemplateRawTree) -> Self {
46 let mut storage = Self {
47 ops: ConstVec::new_with_max_size(),
48 strings: ConstVec::new_with_max_size(),
49 anchors: ConstVec::new_with_max_size(),
50 };
51 let mut cursor = TemplateLoweringCursor::new();
52
53 lower_raw_tree(tree, &mut storage, &mut cursor, false);
54 cursor.finish();
55 storage
56 }
57
58 pub const fn as_template(&'static self) -> Template {
60 Template::new(
61 self.ops.as_slice(),
62 self.strings.as_slice(),
63 self.anchors.as_slice(),
64 )
65 }
66
67 #[cfg(test)]
69 pub(crate) fn into_leaked_template(self) -> Template {
70 Template::new(
71 Box::leak(self.ops.as_slice().to_vec().into_boxed_slice()),
72 Box::leak(self.strings.as_slice().to_vec().into_boxed_slice()),
73 Box::leak(self.anchors.as_slice().to_vec().into_boxed_slice()),
74 )
75 }
76}
77
78#[cfg(debug_assertions)]
85pub fn build_runtime_template(tree: &'static TemplateRawTree) -> Template {
86 let mut builder = RuntimeTemplateBuilder::new();
87 lower_raw_tree_runtime(tree, &mut builder, false);
88 builder.finish()
89}
90
91#[cfg(debug_assertions)]
97fn lower_raw_tree_runtime(
98 tree: &'static TemplateRawTree,
99 builder: &mut RuntimeTemplateBuilder,
100 following_static_at_parent: bool,
101) {
102 match tree {
103 TemplateRawTree::Empty => {}
104 TemplateRawTree::Sequence(children) => {
105 let mut index = 0;
106 while index < children.len() {
107 lower_raw_tree_runtime(
108 children[index],
109 builder,
110 following_static_at_parent
111 || children_have_static_root_node(children, index + 1),
112 );
113 index += 1;
114 }
115 }
116 TemplateRawTree::Element {
117 tag,
118 namespace,
119 attrs,
120 children,
121 } => {
122 builder.open_element(tag, *namespace);
123 lower_raw_tree_runtime(attrs, builder, false);
124 lower_raw_tree_runtime(children, builder, false);
125 builder.close_element();
126 }
127 TemplateRawTree::StaticAttr {
128 name,
129 value,
130 namespace,
131 } => builder.static_attr(name, value, *namespace),
132 TemplateRawTree::DynamicAttr => builder.dynamic_attr(),
133 TemplateRawTree::StaticText(value) => builder.static_text(value),
134 TemplateRawTree::DynamicNode => builder.dynamic_node(following_static_at_parent),
135 }
136}
137
138#[derive(Clone, Copy)]
139pub struct TemplateElementFrame {
140 pub(crate) enter_index: usize,
141 namespace: bool,
142 pub(crate) path: TemplatePath,
143}
144
145pub struct TemplateLoweringCursor {
146 enter_stack: [TemplateElementFrame; TEMPLATE_PATH_STACK_CAP],
147 pub(crate) next_paths: [TemplatePath; TEMPLATE_PATH_STACK_CAP],
148 last_static_paths: [TemplatePath; TEMPLATE_PATH_STACK_CAP],
149 pub(crate) stack_pointer: usize,
150}
151
152impl TemplateLoweringCursor {
153 pub const fn new() -> Self {
154 let mut next_paths = [TemplatePath::empty(); TEMPLATE_PATH_STACK_CAP];
155 next_paths[0] = TemplatePath::root(0);
156 Self {
157 enter_stack: [TemplateElementFrame {
158 enter_index: 0,
159 namespace: false,
160 path: TemplatePath::empty(),
161 }; TEMPLATE_PATH_STACK_CAP],
162 next_paths,
163 last_static_paths: [TemplatePath::empty(); TEMPLATE_PATH_STACK_CAP],
164 stack_pointer: 0,
165 }
166 }
167
168 pub const fn open_element(&mut self, enter_index: usize, namespace: bool) {
169 if self.stack_pointer + 1 >= TEMPLATE_PATH_STACK_CAP {
170 panic!("template path stack capacity exceeded");
171 }
172 let path = self.next_paths[self.stack_pointer];
173 self.next_paths[self.stack_pointer] = path.next_sibling();
174 self.enter_stack[self.stack_pointer] = TemplateElementFrame {
175 enter_index,
176 namespace,
177 path,
178 };
179 self.next_paths[self.stack_pointer + 1] = path.next_child();
180 self.last_static_paths[self.stack_pointer + 1] = TemplatePath::empty();
181 self.last_static_paths[self.stack_pointer] = path;
182 self.stack_pointer += 1;
183 }
184
185 pub const fn close_element(&mut self) -> TemplateElementFrame {
186 if self.stack_pointer == 0 {
187 panic!("template close op without matching open op");
188 }
189 self.stack_pointer -= 1;
190 self.enter_stack[self.stack_pointer]
191 }
192
193 const fn current_element_path(&self) -> TemplatePath {
194 if self.stack_pointer == 0 {
195 panic!("dynamic attr raw op without an open element");
196 }
197 self.current_element_frame().path
198 }
199
200 pub(crate) const fn node_anchor_parent_op_index(&self) -> u16 {
201 if self.stack_pointer == 0 {
202 ROOT_PARENT_OP_INDEX
203 } else {
204 self.current_element_frame().enter_index as u16
205 }
206 }
207
208 pub(crate) const fn current_element_frame(&self) -> TemplateElementFrame {
209 if self.stack_pointer == 0 {
210 panic!("template cursor is not inside an element");
211 }
212 let frame = self.enter_stack[self.stack_pointer - 1];
213 if frame.enter_index > TemplateOp::MAX_CAP {
214 panic!("template enter op exceeds packed op capacity");
215 }
216 frame
217 }
218
219 pub const fn next_node_path(&mut self) -> TemplatePath {
220 let path = self.next_paths[self.stack_pointer];
221 self.next_paths[self.stack_pointer] = path.next_sibling();
222 self.last_static_paths[self.stack_pointer] = path;
223 path
224 }
225
226 const fn next_slot_path_after_dynamic_node(
227 &self,
228 has_following_static_at_parent: bool,
229 ) -> TemplateSlotPath {
230 if has_following_static_at_parent {
231 return TemplateSlotPath::static_node(self.next_paths[self.stack_pointer]);
232 }
233
234 if self.stack_pointer > 0 {
235 return TemplateSlotPath::last_static_node(self.current_element_path());
236 }
237
238 let last_static_path = self.last_static_paths[self.stack_pointer];
239 TemplateSlotPath::last_static_node(last_static_path)
240 }
241
242 pub fn static_root_anchor(&self) -> Option<(u16, TemplateSlotPath)> {
244 let path = self.next_paths[self.stack_pointer];
245 (self.stack_pointer == 0 && !path.is_empty())
246 .then(|| (ROOT_PARENT_OP_INDEX, TemplateSlotPath::static_node(path)))
247 }
248
249 pub fn dynamic_attr_anchor(&self) -> (u16, TemplateSlotPath, bool) {
251 let frame = self.current_element_frame();
252 (
253 frame.enter_index as u16,
254 TemplateSlotPath::static_node(frame.path),
255 frame.path.is_empty(),
256 )
257 }
258
259 pub fn dynamic_node_anchor(
261 &self,
262 following_static_at_parent: bool,
263 ) -> (u16, TemplateSlotPath, bool) {
264 let parent_op_index = self.node_anchor_parent_op_index();
265 if following_static_at_parent {
266 let path = self.next_paths[self.stack_pointer];
267 if path.is_empty() {
268 return (
269 parent_op_index,
270 TemplateSlotPath::last_static_node(TemplatePath::empty()),
271 true,
272 );
273 }
274 return (parent_op_index, TemplateSlotPath::static_node(path), false);
275 }
276
277 if self.stack_pointer > 0 {
278 let path = self.current_element_path();
279 if path.is_empty() {
280 return (
281 parent_op_index,
282 TemplateSlotPath::last_static_node(TemplatePath::empty()),
283 true,
284 );
285 }
286 return (
287 parent_op_index,
288 TemplateSlotPath::last_static_node(path),
289 false,
290 );
291 }
292
293 (
294 parent_op_index,
295 TemplateSlotPath::last_static_node(self.last_static_paths[self.stack_pointer]),
296 false,
297 )
298 }
299
300 pub const fn finish(&self) {
301 if self.stack_pointer != 0 {
302 panic!("template ended with unclosed elements");
303 }
304 }
305}
306
307impl Default for TemplateLoweringCursor {
308 fn default() -> Self {
309 Self::new()
310 }
311}
312
313const fn tree_has_static_root_node(tree: &'static TemplateRawTree) -> bool {
314 match tree {
315 TemplateRawTree::Empty
316 | TemplateRawTree::StaticAttr { .. }
317 | TemplateRawTree::DynamicAttr
318 | TemplateRawTree::DynamicNode => false,
319 TemplateRawTree::Element { .. } | TemplateRawTree::StaticText(_) => true,
320 TemplateRawTree::Sequence(children) => children_have_static_root_node(children, 0),
321 }
322}
323
324const fn children_have_static_root_node(
325 children: &'static [&'static TemplateRawTree],
326 start: usize,
327) -> bool {
328 let mut index = start;
329 while index < children.len() {
330 if tree_has_static_root_node(children[index]) {
331 return true;
332 }
333 index += 1;
334 }
335
336 false
337}
338
339macro_rules! template_lowering {
342 (open_element($storage:expr, $cursor:expr, $tag:expr, $namespace:expr)) => {{
343 let namespace = $namespace;
344 let has_namespace = namespace.is_some();
345 if ($cursor).stack_pointer == 0 {
346 ($storage).push_static_anchor(
347 ROOT_PARENT_OP_INDEX,
348 TemplateSlotPath::static_node(($cursor).next_paths[0]),
349 );
350 }
351 ($cursor).open_element(($storage).ops_len(), has_namespace);
352 ($storage).push_op(TemplateOp::enter(0, has_namespace));
353 ($storage).push_static($tag);
354 if let Some(namespace) = namespace {
355 ($storage).push_static(namespace);
356 }
357 }};
358 (close_element($storage:expr, $cursor:expr)) => {{
359 let frame = ($cursor).close_element();
360 let enter_index = frame.enter_index;
361 let namespace = frame.namespace;
362 let skip = ($storage).ops_len() - enter_index;
363 if skip > TemplateOp::MAX_CAP {
364 panic!("template op skip exceeds packed op capacity");
365 }
366 ($storage).set_op(enter_index, TemplateOp::enter(skip as u16, namespace));
367 }};
368 (static_attr($storage:expr, $name:expr, $value:expr, $namespace:expr)) => {{
369 let namespace = $namespace;
370 ($storage).push_op(TemplateOp::attr(namespace.is_some()));
371 ($storage).push_static($name);
372 ($storage).push_static($value);
373 if let Some(namespace) = namespace {
374 ($storage).push_static(namespace);
375 }
376 }};
377 (static_text($storage:expr, $cursor:expr, $value:expr)) => {{
378 if ($cursor).stack_pointer == 0 {
379 ($storage).push_static_anchor(
380 ROOT_PARENT_OP_INDEX,
381 TemplateSlotPath::static_node(($cursor).next_paths[0]),
382 );
383 }
384 let _ = ($cursor).next_node_path();
385 ($storage).push_op(TemplateOp::text());
386 ($storage).push_static($value);
387 }};
388 (dynamic_node($storage:expr, $cursor:expr, $following_static_at_parent:expr)) => {{
389 let path = ($cursor).next_slot_path_after_dynamic_node($following_static_at_parent);
390 ($storage).push_anchor(($cursor).node_anchor_parent_op_index(), path, false);
391 }};
392}
393
394macro_rules! template_storage_methods {
395 ($($constness:tt)?) => {
396 $($constness)? fn push_static(&mut self, value: &'static str) {
397 let id = self.strings.len();
398 if id >= TemplateOp::MAX_CAP {
399 panic!("static op id exceeds packed op capacity");
400 }
401 self.strings.push(value);
402 self.push_op(TemplateOp::static_text(id as u16));
403 }
404
405 $($constness)? fn ops_len(&self) -> usize {
406 self.ops.len()
407 }
408
409 $($constness)? fn push_op(&mut self, op: TemplateOp) {
410 if self.ops.len() >= TemplateOp::MAX_CAP {
411 panic!("template ops exceed packed op capacity");
412 }
413 self.ops.push(op);
414 }
415
416 $($constness)? fn set_op(&mut self, index: usize, op: TemplateOp) {
417 self.ops.set(index, op);
418 }
419
420 $($constness)? fn push_anchor(
421 &mut self,
422 parent_op_index: u16,
423 path: TemplateSlotPath,
424 is_attr: bool,
425 ) {
426 let len = self.anchors.len();
427 if len > 0 {
428 let last = self.anchors.at(len - 1);
429 if last.same_anchor(parent_op_index, path) {
430 if is_attr {
431 if last.attr_end == u16::MAX {
432 panic!("anchor overflow");
433 }
434 self.anchors.set(
435 len - 1,
436 TemplateAnchor {
437 attr_end: last.attr_end + 1,
438 ..last
439 },
440 );
441 } else {
442 if last.node_end == u16::MAX {
443 panic!("anchor overflow");
444 }
445 self.anchors.set(
446 len - 1,
447 TemplateAnchor {
448 node_end: last.node_end + 1,
449 ..last
450 },
451 );
452 }
453 return;
454 }
455 }
456
457 let (node_start, attr_start) = if len == 0 {
458 (0, 0)
459 } else {
460 let last = self.anchors.at(len - 1);
461 (last.node_end, last.attr_end)
462 };
463 if (is_attr && attr_start == u16::MAX) || (!is_attr && node_start == u16::MAX) {
464 panic!("anchor overflow");
465 }
466 self.anchors.push(TemplateAnchor {
467 parent_op_index,
468 path,
469 node_start,
470 node_end: node_start + (!is_attr as u16),
471 attr_start,
472 attr_end: attr_start + (is_attr as u16),
473 });
474 }
475
476 $($constness)? fn push_static_anchor(
477 &mut self,
478 parent_op_index: u16,
479 path: TemplateSlotPath,
480 ) {
481 let len = self.anchors.len();
482 if len > 0 {
483 let last = self.anchors.at(len - 1);
484 if last.same_anchor(parent_op_index, path) {
485 return;
486 }
487 }
488
489 let (node_start, attr_start) = if len == 0 {
490 (0, 0)
491 } else {
492 let last = self.anchors.at(len - 1);
493 (last.node_end, last.attr_end)
494 };
495 self.anchors.push(TemplateAnchor {
496 parent_op_index,
497 path,
498 node_start,
499 node_end: node_start,
500 attr_start,
501 attr_end: attr_start,
502 });
503 }
504
505 };
506}
507
508impl<const OPS_CAP: usize, const STRING_CAP: usize, const DYNAMIC_CAP: usize>
509 TemplateStorage<OPS_CAP, STRING_CAP, DYNAMIC_CAP>
510{
511 template_storage_methods!(const);
512}
513
514const fn push_element_start<
515 const OPS_CAP: usize,
516 const STRING_CAP: usize,
517 const DYNAMIC_CAP: usize,
518>(
519 storage: &mut TemplateStorage<OPS_CAP, STRING_CAP, DYNAMIC_CAP>,
520 cursor: &mut TemplateLoweringCursor,
521 tag: &'static str,
522 namespace: Option<&'static str>,
523) {
524 template_lowering!(open_element(storage, cursor, tag, namespace));
525}
526
527const fn push_element_end<
528 const OPS_CAP: usize,
529 const STRING_CAP: usize,
530 const DYNAMIC_CAP: usize,
531>(
532 storage: &mut TemplateStorage<OPS_CAP, STRING_CAP, DYNAMIC_CAP>,
533 cursor: &mut TemplateLoweringCursor,
534) {
535 template_lowering!(close_element(storage, cursor));
536}
537
538const fn push_static_attr<
539 const OPS_CAP: usize,
540 const STRING_CAP: usize,
541 const DYNAMIC_CAP: usize,
542>(
543 storage: &mut TemplateStorage<OPS_CAP, STRING_CAP, DYNAMIC_CAP>,
544 name: &'static str,
545 value: &'static str,
546 namespace: Option<&'static str>,
547) {
548 template_lowering!(static_attr(storage, name, value, namespace));
549}
550
551const fn lower_raw_tree<const OPS_CAP: usize, const STRING_CAP: usize, const DYNAMIC_CAP: usize>(
552 tree: &'static TemplateRawTree,
553 storage: &mut TemplateStorage<OPS_CAP, STRING_CAP, DYNAMIC_CAP>,
554 cursor: &mut TemplateLoweringCursor,
555 following_static_at_parent: bool,
556) {
557 match tree {
558 TemplateRawTree::Empty => {}
559 TemplateRawTree::Sequence(children) => {
560 let mut index = 0;
561 while index < children.len() {
562 lower_raw_tree(
563 children[index],
564 storage,
565 cursor,
566 following_static_at_parent
567 || children_have_static_root_node(children, index + 1),
568 );
569 index += 1;
570 }
571 }
572 TemplateRawTree::Element {
573 tag,
574 namespace,
575 attrs,
576 children,
577 } => {
578 push_element_start(storage, cursor, tag, *namespace);
579 lower_raw_tree(attrs, storage, cursor, false);
580 lower_raw_tree(children, storage, cursor, false);
581 push_element_end(storage, cursor);
582 }
583 TemplateRawTree::StaticAttr {
584 name,
585 value,
586 namespace,
587 } => {
588 push_static_attr(storage, name, value, *namespace);
589 }
590 TemplateRawTree::DynamicAttr => {
591 let frame = cursor.current_element_frame();
592 let path = TemplateSlotPath::static_node(frame.path);
593 storage.push_anchor(frame.enter_index as u16, path, true);
594 }
595 TemplateRawTree::StaticText(value) => {
596 template_lowering!(static_text(storage, cursor, value));
597 }
598 TemplateRawTree::DynamicNode => {
599 template_lowering!(dynamic_node(storage, cursor, following_static_at_parent));
600 }
601 }
602}
603
604pub struct RuntimeTemplateBuilder {
606 storage: RuntimeTemplateStorage,
607 cursor: TemplateLoweringCursor,
608}
609
610#[derive(Default)]
611struct RuntimeTemplateStorage {
612 ops: RuntimeTemplateVec<TemplateOp>,
613 strings: RuntimeTemplateVec<&'static str>,
614 anchors: RuntimeTemplateVec<TemplateAnchor>,
615}
616
617struct RuntimeTemplateVec<T>(Vec<T>);
618
619impl<T> Default for RuntimeTemplateVec<T> {
620 fn default() -> Self {
621 Self(Vec::new())
622 }
623}
624
625impl<T: Copy> RuntimeTemplateVec<T> {
626 fn len(&self) -> usize {
627 self.0.len()
628 }
629
630 fn push(&mut self, value: T) {
631 self.0.push(value);
632 }
633
634 fn set(&mut self, index: usize, value: T) {
635 self.0[index] = value;
636 }
637
638 fn at(&self, index: usize) -> T {
639 self.0[index]
640 }
641
642 fn into_boxed_slice(self) -> Box<[T]> {
643 self.0.into_boxed_slice()
644 }
645}
646
647impl Default for RuntimeTemplateBuilder {
648 fn default() -> Self {
649 Self::new()
650 }
651}
652
653impl RuntimeTemplateBuilder {
654 fn new() -> Self {
656 Self {
657 storage: RuntimeTemplateStorage::default(),
658 cursor: TemplateLoweringCursor::new(),
659 }
660 }
661
662 pub fn open_element(&mut self, tag: &'static str, namespace: Option<&'static str>) {
664 template_lowering!(open_element(
665 &mut self.storage,
666 &mut self.cursor,
667 tag,
668 namespace
669 ));
670 }
671
672 pub fn close_element(&mut self) {
674 template_lowering!(close_element(&mut self.storage, &mut self.cursor));
675 }
676
677 pub fn static_attr(
679 &mut self,
680 name: &'static str,
681 value: &'static str,
682 namespace: Option<&'static str>,
683 ) {
684 template_lowering!(static_attr(&mut self.storage, name, value, namespace));
685 }
686
687 pub fn dynamic_attr(&mut self) {
689 let frame = self.cursor.current_element_frame();
690 let path = TemplateSlotPath::static_node(frame.path);
691 self.storage
692 .push_anchor(frame.enter_index as u16, path, true);
693 }
694
695 pub fn static_text(&mut self, value: &'static str) {
697 template_lowering!(static_text(&mut self.storage, &mut self.cursor, value));
698 }
699
700 pub fn dynamic_node(&mut self, following_static_at_parent: bool) {
702 template_lowering!(dynamic_node(
703 &mut self.storage,
704 &mut self.cursor,
705 following_static_at_parent
706 ));
707 }
708
709 pub fn finish(self) -> Template {
711 self.cursor.finish();
712 self.storage.into_leaked_template()
713 }
714}
715
716impl RuntimeTemplateStorage {
717 fn into_leaked_template(self) -> Template {
718 Template::new(
719 Box::leak(self.ops.into_boxed_slice()),
720 Box::leak(self.strings.into_boxed_slice()),
721 Box::leak(self.anchors.into_boxed_slice()),
722 )
723 }
724
725 template_storage_methods!();
726}
727
728#[cfg(test)]
729mod tests {
730 use super::*;
731
732 fn template_from_tree(tree: &'static TemplateRawTree) -> Template {
733 TemplateStorage::<64, 64, 16>::build_from_tree(tree).into_leaked_template()
734 }
735
736 fn anchor_parts(template: Template) -> Vec<(u16, u128, u16, u16, u16, u16)> {
737 template
738 .anchors()
739 .iter()
740 .map(|anchor| {
741 (
742 anchor.parent_op_index,
743 anchor.path.bits(),
744 anchor.node_start,
745 anchor.node_end,
746 anchor.attr_start,
747 anchor.attr_end,
748 )
749 })
750 .collect()
751 }
752
753 fn assert_same_template(actual: Template, expected: Template) {
754 assert_eq!(
755 actual.decoded_ops().collect::<Vec<_>>(),
756 expected.decoded_ops().collect::<Vec<_>>()
757 );
758 assert_eq!(actual.strings(), expected.strings());
759 assert_eq!(anchor_parts(actual), anchor_parts(expected));
760 }
761
762 #[test]
763 fn runtime_builder_matches_tree_for_nested_namespaces_and_dynamic_attrs() {
764 static ATTR: TemplateRawTree = TemplateRawTree::StaticAttr {
765 name: "fill",
766 value: "red",
767 namespace: Some("style"),
768 };
769 static ATTRS: [&TemplateRawTree; 2] = [&ATTR, &TemplateRawTree::DynamicAttr];
770 static ATTRS_TREE: TemplateRawTree = TemplateRawTree::Sequence(&ATTRS);
771 static TEXT: TemplateRawTree = TemplateRawTree::StaticText("hello");
772 static INNER_CHILDREN: [&TemplateRawTree; 1] = [&TemplateRawTree::DynamicNode];
773 static INNER_CHILDREN_TREE: TemplateRawTree = TemplateRawTree::Sequence(&INNER_CHILDREN);
774 static INNER: TemplateRawTree = TemplateRawTree::Element {
775 tag: "span",
776 namespace: None,
777 attrs: &TemplateRawTree::Empty,
778 children: &INNER_CHILDREN_TREE,
779 };
780 static CHILDREN: [&TemplateRawTree; 2] = [&TEXT, &INNER];
781 static CHILDREN_TREE: TemplateRawTree = TemplateRawTree::Sequence(&CHILDREN);
782 static TREE: TemplateRawTree = TemplateRawTree::Element {
783 tag: "svg",
784 namespace: Some("svg"),
785 attrs: &ATTRS_TREE,
786 children: &CHILDREN_TREE,
787 };
788
789 let mut builder = RuntimeTemplateBuilder::new();
790 builder.open_element("svg", Some("svg"));
791 builder.static_attr("fill", "red", Some("style"));
792 builder.dynamic_attr();
793 builder.static_text("hello");
794 builder.open_element("span", None);
795 builder.dynamic_node(false);
796 builder.close_element();
797 builder.close_element();
798
799 assert_same_template(builder.finish(), template_from_tree(&TREE));
800 }
801
802 #[test]
803 fn runtime_builder_places_dynamic_nodes_before_static_siblings() {
804 static TEXT: TemplateRawTree = TemplateRawTree::StaticText("after");
805 static CHILDREN: [&TemplateRawTree; 2] = [&TemplateRawTree::DynamicNode, &TEXT];
806 static TREE: TemplateRawTree = TemplateRawTree::Sequence(&CHILDREN);
807
808 let mut builder = RuntimeTemplateBuilder::new();
809 builder.dynamic_node(true);
810 builder.static_text("after");
811
812 assert_same_template(builder.finish(), template_from_tree(&TREE));
813 }
814
815 #[test]
816 fn runtime_builder_groups_adjacent_trailing_dynamic_nodes() {
817 static CHILDREN: [&TemplateRawTree; 2] =
818 [&TemplateRawTree::DynamicNode, &TemplateRawTree::DynamicNode];
819 static TREE: TemplateRawTree = TemplateRawTree::Sequence(&CHILDREN);
820
821 let mut builder = RuntimeTemplateBuilder::new();
822 builder.dynamic_node(false);
823 builder.dynamic_node(false);
824
825 let template = builder.finish();
826 assert_same_template(template, template_from_tree(&TREE));
827 assert_eq!(template.anchors()[0].nodes(), 0..2);
828 }
829}