1use super::{Key, VDiff, VNode};
4use crate::html::{AnyScope, Component, NodeRef, Scope, Scoped};
5use cfg_if::cfg_if;
6use std::any::TypeId;
7use std::borrow::Borrow;
8use std::fmt;
9use std::ops::Deref;
10cfg_if! {
11 if #[cfg(feature = "std_web")] {
12 use stdweb::web::Element;
13 } else if #[cfg(feature = "web_sys")] {
14 use web_sys::Element;
15 }
16}
17
18pub struct VComp {
20 type_id: TypeId,
21 scope: Option<Box<dyn Scoped>>,
22 props: Option<Box<dyn Mountable>>,
23 pub(crate) node_ref: NodeRef,
24 pub(crate) key: Option<Key>,
25}
26
27impl Clone for VComp {
28 fn clone(&self) -> Self {
29 if self.scope.is_some() {
30 panic!("Mounted components are not allowed to be cloned!");
31 }
32
33 Self {
34 type_id: self.type_id,
35 scope: None,
36 props: self.props.as_ref().map(|m| m.copy()),
37 node_ref: self.node_ref.clone(),
38 key: self.key.clone(),
39 }
40 }
41}
42
43pub struct VChild<COMP: Component> {
45 pub props: COMP::Properties,
47 node_ref: NodeRef,
49 key: Option<Key>,
50}
51
52impl<COMP: Component> Clone for VChild<COMP> {
53 fn clone(&self) -> Self {
54 VChild {
55 props: self.props.clone(),
56 node_ref: self.node_ref.clone(),
57 key: self.key.clone(),
58 }
59 }
60}
61
62impl<COMP: Component> PartialEq for VChild<COMP>
63where
64 COMP::Properties: PartialEq,
65{
66 fn eq(&self, other: &VChild<COMP>) -> bool {
67 self.props == other.props
68 }
69}
70
71impl<COMP> VChild<COMP>
72where
73 COMP: Component,
74{
75 pub fn new(props: COMP::Properties, node_ref: NodeRef, key: Option<Key>) -> Self {
77 Self {
78 props,
79 node_ref,
80 key,
81 }
82 }
83}
84
85impl<COMP> From<VChild<COMP>> for VComp
86where
87 COMP: Component,
88{
89 fn from(vchild: VChild<COMP>) -> Self {
90 VComp::new::<COMP>(vchild.props, vchild.node_ref, vchild.key)
91 }
92}
93
94impl VComp {
95 pub fn new<COMP>(props: COMP::Properties, node_ref: NodeRef, key: Option<Key>) -> Self
97 where
98 COMP: Component,
99 {
100 VComp {
101 type_id: TypeId::of::<COMP>(),
102 node_ref,
103 props: Some(Box::new(PropsWrapper::<COMP>::new(props))),
104 scope: None,
105 key,
106 }
107 }
108
109 #[allow(unused)]
110 pub(crate) fn root_vnode(&self) -> Option<impl Deref<Target = VNode> + '_> {
111 self.scope.as_ref().and_then(|scope| scope.root_vnode())
112 }
113}
114
115trait Mountable {
116 fn copy(&self) -> Box<dyn Mountable>;
117 fn mount(
118 self: Box<Self>,
119 node_ref: NodeRef,
120 parent_scope: &AnyScope,
121 parent: Element,
122 next_sibling: NodeRef,
123 ) -> Box<dyn Scoped>;
124 fn reuse(self: Box<Self>, node_ref: NodeRef, scope: &dyn Scoped, next_sibling: NodeRef);
125}
126
127struct PropsWrapper<COMP: Component> {
128 props: COMP::Properties,
129}
130
131impl<COMP: Component> PropsWrapper<COMP> {
132 pub fn new(props: COMP::Properties) -> Self {
133 Self { props }
134 }
135}
136
137impl<COMP: Component> Mountable for PropsWrapper<COMP> {
138 fn copy(&self) -> Box<dyn Mountable> {
139 let wrapper: PropsWrapper<COMP> = PropsWrapper {
140 props: self.props.clone(),
141 };
142 Box::new(wrapper)
143 }
144
145 fn mount(
146 self: Box<Self>,
147 node_ref: NodeRef,
148 parent_scope: &AnyScope,
149 parent: Element,
150 next_sibling: NodeRef,
151 ) -> Box<dyn Scoped> {
152 let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
153 let scope = scope.mount_in_place(parent, next_sibling, node_ref, self.props);
154
155 Box::new(scope)
156 }
157
158 fn reuse(self: Box<Self>, node_ref: NodeRef, scope: &dyn Scoped, next_sibling: NodeRef) {
159 let scope: Scope<COMP> = scope.to_any().downcast();
160 scope.reuse(self.props, node_ref, next_sibling);
161 }
162}
163
164impl VDiff for VComp {
165 fn detach(&mut self, _parent: &Element) {
166 self.scope.take().expect("VComp is not mounted").destroy();
167 }
168
169 fn apply(
170 &mut self,
171 parent_scope: &AnyScope,
172 parent: &Element,
173 next_sibling: NodeRef,
174 ancestor: Option<VNode>,
175 ) -> NodeRef {
176 let mountable = self.props.take().expect("VComp has already been mounted");
177
178 if let Some(mut ancestor) = ancestor {
179 if let VNode::VComp(ref mut vcomp) = &mut ancestor {
180 if self.type_id == vcomp.type_id && self.key == vcomp.key {
182 self.node_ref.reuse(vcomp.node_ref.clone());
183 let scope = vcomp.scope.take().expect("VComp is not mounted");
184 mountable.reuse(self.node_ref.clone(), scope.borrow(), next_sibling);
185 self.scope = Some(scope);
186 return vcomp.node_ref.clone();
187 }
188 }
189
190 ancestor.detach(parent);
191 }
192
193 self.scope = Some(mountable.mount(
194 self.node_ref.clone(),
195 parent_scope,
196 parent.to_owned(),
197 next_sibling,
198 ));
199
200 self.node_ref.clone()
201 }
202}
203
204impl PartialEq for VComp {
205 fn eq(&self, other: &VComp) -> bool {
206 self.type_id == other.type_id
207 }
208}
209
210impl fmt::Debug for VComp {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 f.write_str("VComp")
213 }
214}
215
216impl<COMP: Component> fmt::Debug for VChild<COMP> {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 f.write_str("VChild<_>")
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::{
226 html, utils::document, Children, Component, ComponentLink, Html, NodeRef, Properties,
227 ShouldRender,
228 };
229 use cfg_match::cfg_match;
230
231 cfg_if! {
232 if #[cfg(feature = "std_web")] {
233 use stdweb::web::{INode, Node};
234 } else if #[cfg(feature = "web_sys")] {
235 use web_sys::Node;
236 }
237 }
238
239 #[cfg(feature = "wasm_test")]
240 use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
241
242 #[cfg(feature = "wasm_test")]
243 wasm_bindgen_test_configure!(run_in_browser);
244
245 struct Comp;
246
247 #[derive(Clone, PartialEq, Properties)]
248 struct Props {
249 #[prop_or_default]
250 field_1: u32,
251 #[prop_or_default]
252 field_2: u32,
253 }
254
255 impl Component for Comp {
256 type Message = ();
257 type Properties = Props;
258
259 fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
260 Comp
261 }
262
263 fn update(&mut self, _: Self::Message) -> ShouldRender {
264 unimplemented!();
265 }
266
267 fn change(&mut self, _: Self::Properties) -> ShouldRender {
268 true
269 }
270
271 fn view(&self) -> Html {
272 html! { <div/> }
273 }
274 }
275
276 #[test]
277 fn update_loop() {
278 let document = crate::utils::document();
279 let parent_scope: AnyScope = crate::html::Scope::<Comp>::new(None).into();
280 let parent_element = document.create_element("div").unwrap();
281
282 let mut ancestor = html! { <Comp></Comp> };
283 ancestor.apply(&parent_scope, &parent_element, NodeRef::default(), None);
284
285 for _ in 0..10000 {
286 let mut node = html! { <Comp></Comp> };
287 node.apply(
288 &parent_scope,
289 &parent_element,
290 NodeRef::default(),
291 Some(ancestor),
292 );
293 ancestor = node;
294 }
295 }
296
297 #[test]
298 fn set_properties_to_component() {
299 html! {
300 <Comp />
301 };
302
303 html! {
304 <Comp field_1=1 />
305 };
306
307 html! {
308 <Comp field_2=2 />
309 };
310
311 html! {
312 <Comp field_1=1 field_2=2 />
313 };
314
315 let props = Props {
316 field_1: 1,
317 field_2: 1,
318 };
319
320 html! {
321 <Comp with props />
322 };
323 }
324
325 #[test]
326 fn set_component_key() {
327 let test_key: Key = "test".to_string().into();
328 let check_key = |vnode: VNode| {
329 assert_eq!(vnode.key().as_ref(), Some(&test_key));
330 };
331
332 let props = Props {
333 field_1: 1,
334 field_2: 1,
335 };
336 let props_2 = props.clone();
337
338 check_key(html! { <Comp key=test_key.clone() /> });
339 check_key(html! { <Comp key=test_key.clone() field_1=1 /> });
340 check_key(html! { <Comp field_1=1 key=test_key.clone() /> });
341 check_key(html! { <Comp with props key=test_key.clone() /> });
342 check_key(html! { <Comp key=test_key.clone() with props_2 /> });
343 }
344
345 #[test]
346 fn set_component_node_ref() {
347 let test_node: Node = document().create_text_node("test").into();
348 let test_node_ref = NodeRef::new(test_node);
349 let check_node_ref = |vnode: VNode| {
350 assert_eq!(vnode.first_node(), test_node_ref.get().unwrap());
351 };
352
353 let props = Props {
354 field_1: 1,
355 field_2: 1,
356 };
357 let props_2 = props.clone();
358
359 check_node_ref(html! { <Comp ref=test_node_ref.clone() /> });
360 check_node_ref(html! { <Comp ref=test_node_ref.clone() field_1=1 /> });
361 check_node_ref(html! { <Comp field_1=1 ref=test_node_ref.clone() /> });
362 check_node_ref(html! { <Comp with props ref=test_node_ref.clone() /> });
363 check_node_ref(html! { <Comp ref=test_node_ref.clone() with props_2 /> });
364 }
365
366 #[test]
367 fn vchild_partialeq() {
368 let vchild1: VChild<Comp> = VChild::new(
369 Props {
370 field_1: 1,
371 field_2: 1,
372 },
373 NodeRef::default(),
374 None,
375 );
376
377 let vchild2: VChild<Comp> = VChild::new(
378 Props {
379 field_1: 1,
380 field_2: 1,
381 },
382 NodeRef::default(),
383 None,
384 );
385
386 let vchild3: VChild<Comp> = VChild::new(
387 Props {
388 field_1: 2,
389 field_2: 2,
390 },
391 NodeRef::default(),
392 None,
393 );
394
395 assert_eq!(vchild1, vchild2);
396 assert_ne!(vchild1, vchild3);
397 assert_ne!(vchild2, vchild3);
398 }
399
400 #[derive(Clone, Properties)]
401 pub struct ListProps {
402 pub children: Children,
403 }
404 pub struct List(ListProps);
405 impl Component for List {
406 type Message = ();
407 type Properties = ListProps;
408
409 fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
410 Self(props)
411 }
412 fn update(&mut self, _: Self::Message) -> ShouldRender {
413 unimplemented!();
414 }
415 fn change(&mut self, _: Self::Properties) -> ShouldRender {
416 unimplemented!();
417 }
418 fn view(&self) -> Html {
419 let item_iter = self.0.children.iter().map(|item| html! {<li>{ item }</li>});
420 html! {
421 <ul>{ for item_iter }</ul>
422 }
423 }
424 }
425
426 #[cfg(feature = "web_sys")]
427 use super::{AnyScope, Element};
428
429 #[cfg(feature = "web_sys")]
430 fn setup_parent() -> (AnyScope, Element) {
431 let scope = AnyScope {
432 type_id: std::any::TypeId::of::<()>(),
433 parent: None,
434 state: std::rc::Rc::new(()),
435 };
436 let parent = document().create_element("div").unwrap();
437
438 document().body().unwrap().append_child(&parent).unwrap();
439
440 (scope, parent)
441 }
442
443 #[cfg(feature = "web_sys")]
444 fn get_html(mut node: Html, scope: &AnyScope, parent: &Element) -> String {
445 parent.set_inner_html("");
447
448 node.apply(&scope, &parent, NodeRef::default(), None);
449 parent.inner_html()
450 }
451
452 #[test]
453 #[cfg(feature = "web_sys")]
454 fn all_ways_of_passing_children_work() {
455 let (scope, parent) = setup_parent();
456
457 let children: Vec<_> = vec!["a", "b", "c"]
458 .drain(..)
459 .map(|text| html! {<span>{ text }</span>})
460 .collect();
461 let children_renderer = Children::new(children.clone());
462 let expected_html = "\
463 <ul>\
464 <li><span>a</span></li>\
465 <li><span>b</span></li>\
466 <li><span>c</span></li>\
467 </ul>";
468
469 let prop_method = html! {
470 <List children=children_renderer.clone()/>
471 };
472 assert_eq!(get_html(prop_method, &scope, &parent), expected_html);
473
474 let children_renderer_method = html! {
475 <List>
476 { children_renderer }
477 </List>
478 };
479 assert_eq!(
480 get_html(children_renderer_method, &scope, &parent),
481 expected_html
482 );
483
484 let direct_method = html! {
485 <List>
486 { children.clone() }
487 </List>
488 };
489 assert_eq!(get_html(direct_method, &scope, &parent), expected_html);
490
491 let for_method = html! {
492 <List>
493 { for children }
494 </List>
495 };
496 assert_eq!(get_html(for_method, &scope, &parent), expected_html);
497 }
498
499 #[test]
500 fn reset_node_ref() {
501 let scope = AnyScope {
502 type_id: std::any::TypeId::of::<()>(),
503 parent: None,
504 state: std::rc::Rc::new(()),
505 };
506 let parent = document().create_element("div").unwrap();
507
508 #[cfg(feature = "std_web")]
509 document().body().unwrap().append_child(&parent);
510 #[cfg(feature = "web_sys")]
511 document().body().unwrap().append_child(&parent).unwrap();
512
513 let node_ref = NodeRef::default();
514 let mut elem: VNode = html! { <Comp ref=node_ref.clone()></Comp> };
515 elem.apply(&scope, &parent, NodeRef::default(), None);
516 let parent_node = cfg_match! {
517 feature = "std_web" => parent.as_node(),
518 feature = "web_sys" => parent.deref(),
519 };
520 assert_eq!(node_ref.get(), parent_node.first_child());
521 elem.detach(&parent);
522 assert!(node_ref.get().is_none());
523 }
524}
525
526#[cfg(all(test, feature = "web_sys"))]
527mod layout_tests {
528 extern crate self as yew;
529
530 use crate::html;
531 use crate::virtual_dom::layout_tests::{diff_layouts, TestLayout};
532 use crate::{Children, Component, ComponentLink, Html, Properties, ShouldRender};
533 use std::marker::PhantomData;
534
535 #[cfg(feature = "wasm_test")]
536 use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
537
538 #[cfg(feature = "wasm_test")]
539 wasm_bindgen_test_configure!(run_in_browser);
540
541 struct Comp<T> {
542 _marker: PhantomData<T>,
543 props: CompProps,
544 }
545
546 #[derive(Properties, Clone)]
547 struct CompProps {
548 #[prop_or_default]
549 children: Children,
550 }
551
552 impl<T: 'static> Component for Comp<T> {
553 type Message = ();
554 type Properties = CompProps;
555
556 fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
557 Comp {
558 _marker: PhantomData::default(),
559 props,
560 }
561 }
562
563 fn update(&mut self, _: Self::Message) -> ShouldRender {
564 unimplemented!();
565 }
566
567 fn change(&mut self, props: Self::Properties) -> ShouldRender {
568 self.props = props;
569 true
570 }
571
572 fn view(&self) -> Html {
573 html! {
574 <>{ self.props.children.clone() }</>
575 }
576 }
577 }
578
579 struct A;
580 struct B;
581
582 #[test]
583 fn diff() {
584 let layout1 = TestLayout {
585 name: "1",
586 node: html! {
587 <Comp<A>>
588 <Comp<B>></Comp<B>>
589 {"C"}
590 </Comp<A>>
591 },
592 expected: "C",
593 };
594
595 let layout2 = TestLayout {
596 name: "2",
597 node: html! {
598 <Comp<A>>
599 {"A"}
600 </Comp<A>>
601 },
602 expected: "A",
603 };
604
605 let layout3 = TestLayout {
606 name: "3",
607 node: html! {
608 <Comp<B>>
609 <Comp<A>></Comp<A>>
610 {"B"}
611 </Comp<B>>
612 },
613 expected: "B",
614 };
615
616 let layout4 = TestLayout {
617 name: "4",
618 node: html! {
619 <Comp<B>>
620 <Comp<A>>{"A"}</Comp<A>>
621 {"B"}
622 </Comp<B>>
623 },
624 expected: "AB",
625 };
626
627 let layout5 = TestLayout {
628 name: "5",
629 node: html! {
630 <Comp<B>>
631 <>
632 <Comp<A>>
633 {"A"}
634 </Comp<A>>
635 </>
636 {"B"}
637 </Comp<B>>
638 },
639 expected: "AB",
640 };
641
642 let layout6 = TestLayout {
643 name: "6",
644 node: html! {
645 <Comp<B>>
646 <>
647 <Comp<A>>
648 {"A"}
649 </Comp<A>>
650 {"B"}
651 </>
652 {"C"}
653 </Comp<B>>
654 },
655 expected: "ABC",
656 };
657
658 let layout7 = TestLayout {
659 name: "7",
660 node: html! {
661 <Comp<B>>
662 <>
663 <Comp<A>>
664 {"A"}
665 </Comp<A>>
666 <Comp<A>>
667 {"B"}
668 </Comp<A>>
669 </>
670 {"C"}
671 </Comp<B>>
672 },
673 expected: "ABC",
674 };
675
676 let layout8 = TestLayout {
677 name: "8",
678 node: html! {
679 <Comp<B>>
680 <>
681 <Comp<A>>
682 {"A"}
683 </Comp<A>>
684 <Comp<A>>
685 <Comp<A>>
686 {"B"}
687 </Comp<A>>
688 </Comp<A>>
689 </>
690 {"C"}
691 </Comp<B>>
692 },
693 expected: "ABC",
694 };
695
696 let layout9 = TestLayout {
697 name: "9",
698 node: html! {
699 <Comp<B>>
700 <>
701 <>
702 {"A"}
703 </>
704 <Comp<A>>
705 <Comp<A>>
706 {"B"}
707 </Comp<A>>
708 </Comp<A>>
709 </>
710 {"C"}
711 </Comp<B>>
712 },
713 expected: "ABC",
714 };
715
716 let layout10 = TestLayout {
717 name: "10",
718 node: html! {
719 <Comp<B>>
720 <>
721 <Comp<A>>
722 <Comp<A>>
723 {"A"}
724 </Comp<A>>
725 </Comp<A>>
726 <>
727 {"B"}
728 </>
729 </>
730 {"C"}
731 </Comp<B>>
732 },
733 expected: "ABC",
734 };
735
736 let layout11 = TestLayout {
737 name: "11",
738 node: html! {
739 <Comp<B>>
740 <>
741 <>
742 <Comp<A>>
743 <Comp<A>>
744 {"A"}
745 </Comp<A>>
746 {"B"}
747 </Comp<A>>
748 </>
749 </>
750 {"C"}
751 </Comp<B>>
752 },
753 expected: "ABC",
754 };
755
756 let layout12 = TestLayout {
757 name: "12",
758 node: html! {
759 <Comp<B>>
760 <>
761 <Comp<A>></Comp<A>>
762 <>
763 <Comp<A>>
764 <>
765 <Comp<A>>
766 {"A"}
767 </Comp<A>>
768 <></>
769 <Comp<A>>
770 <Comp<A>></Comp<A>>
771 <></>
772 {"B"}
773 <></>
774 <Comp<A>></Comp<A>>
775 </Comp<A>>
776 </>
777 </Comp<A>>
778 <></>
779 </>
780 <Comp<A>></Comp<A>>
781 </>
782 {"C"}
783 <Comp<A>></Comp<A>>
784 <></>
785 </Comp<B>>
786 },
787 expected: "ABC",
788 };
789
790 diff_layouts(vec![
791 layout1, layout2, layout3, layout4, layout5, layout6, layout7, layout8, layout9,
792 layout10, layout11, layout12,
793 ]);
794 }
795}