1use std::any::TypeId;
4use std::borrow::Borrow;
5use std::fmt;
6
7use web_sys::Element;
8
9use super::{BNode, BSubtree, DomSlot, DynamicDomSlot, Reconcilable, ReconcileTarget};
10use crate::html::{AnyScope, Scoped};
11use crate::virtual_dom::{Key, VComp};
12
13pub(super) struct BComp {
15 type_id: TypeId,
16 scope: Box<dyn Scoped>,
17 own_position: DynamicDomSlot,
20 key: Option<Key>,
21}
22
23impl BComp {
24 pub fn key(&self) -> Option<&Key> {
26 self.key.as_ref()
27 }
28}
29
30impl fmt::Debug for BComp {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 f.debug_struct("BComp")
33 .field("root", &self.scope.as_ref().render_state())
34 .finish()
35 }
36}
37
38impl ReconcileTarget for BComp {
39 fn detach(self, _root: &BSubtree, _parent: &Element, parent_to_detach: bool) {
40 self.scope.destroy_boxed(parent_to_detach);
41 }
42
43 fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
44 self.scope.shift_node(next_parent.clone(), slot);
45
46 self.own_position.to_position()
47 }
48}
49
50impl Reconcilable for VComp {
51 type Bundle = BComp;
52
53 fn attach(
54 self,
55 root: &BSubtree,
56 parent_scope: &AnyScope,
57 parent: &Element,
58 slot: DomSlot,
59 ) -> (DomSlot, Self::Bundle) {
60 let VComp {
61 type_id,
62 mountable,
63 key,
64 ..
65 } = self;
66 let internal_ref = DynamicDomSlot::new_debug_trapped();
67
68 let scope = mountable.mount(
69 root,
70 parent_scope,
71 parent.to_owned(),
72 slot,
73 internal_ref.clone(),
74 );
75
76 (
77 internal_ref.to_position(),
78 BComp {
79 type_id,
80 own_position: internal_ref,
81 key,
82 scope,
83 },
84 )
85 }
86
87 fn reconcile_node(
88 self,
89 root: &BSubtree,
90 parent_scope: &AnyScope,
91 parent: &Element,
92 slot: DomSlot,
93 bundle: &mut BNode,
94 ) -> DomSlot {
95 match bundle {
96 BNode::Comp(ref mut bcomp)
98 if self.type_id == bcomp.type_id && self.key == bcomp.key =>
99 {
100 self.reconcile(root, parent_scope, parent, slot, bcomp)
101 }
102 _ => self.replace(root, parent_scope, parent, slot, bundle),
103 }
104 }
105
106 fn reconcile(
107 self,
108 _root: &BSubtree,
109 _parent_scope: &AnyScope,
110 _parent: &Element,
111 slot: DomSlot,
112 bcomp: &mut Self::Bundle,
113 ) -> DomSlot {
114 let VComp { mountable, key, .. } = self;
115
116 bcomp.key = key;
117 mountable.reuse(bcomp.scope.borrow(), slot);
118 bcomp.own_position.to_position()
119 }
120}
121
122#[cfg(feature = "hydration")]
123mod feat_hydration {
124 use super::*;
125 use crate::dom_bundle::{Fragment, Hydratable};
126
127 impl Hydratable for VComp {
128 fn hydrate(
129 self,
130 root: &BSubtree,
131 parent_scope: &AnyScope,
132 parent: &Element,
133 fragment: &mut Fragment,
134 ) -> Self::Bundle {
135 let VComp {
136 type_id,
137 mountable,
138 key,
139 ..
140 } = self;
141 let internal_ref = DynamicDomSlot::new_debug_trapped();
142
143 let scoped = mountable.hydrate(
144 root.clone(),
145 parent_scope,
146 parent.clone(),
147 internal_ref.clone(),
148 fragment,
149 );
150
151 BComp {
152 type_id,
153 scope: scoped,
154 own_position: internal_ref,
155 key,
156 }
157 }
158 }
159}
160
161#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
162#[cfg(test)]
163mod tests {
164 use gloo::utils::document;
165 use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
166 use web_sys::Element;
167
168 use super::*;
169 use crate::dom_bundle::Reconcilable;
170 use crate::virtual_dom::{Key, VChild, VNode};
171 use crate::{html, scheduler, Children, Component, Context, Html, Properties};
172
173 wasm_bindgen_test_configure!(run_in_browser);
174
175 struct Comp;
176
177 #[derive(Clone, PartialEq, Properties)]
178 struct Props {
179 #[prop_or_default]
180 field_1: u32,
181 #[prop_or_default]
182 field_2: u32,
183 }
184
185 impl Component for Comp {
186 type Message = ();
187 type Properties = Props;
188
189 fn create(_: &Context<Self>) -> Self {
190 Comp
191 }
192
193 fn update(&mut self, _ctx: &Context<Self>, _: Self::Message) -> bool {
194 unimplemented!();
195 }
196
197 fn view(&self, _ctx: &Context<Self>) -> Html {
198 html! { <div/> }
199 }
200 }
201
202 #[test]
203 fn update_loop() {
204 let (root, scope, parent) = setup_parent();
205
206 let comp = html! { <Comp></Comp> };
207 let (_, mut bundle) = comp.attach(&root, &scope, &parent, DomSlot::at_end());
208 scheduler::start_now();
209
210 for _ in 0..10000 {
211 let node = html! { <Comp></Comp> };
212 node.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut bundle);
213 scheduler::start_now();
214 }
215 }
216
217 #[test]
218 fn set_properties_to_component() {
219 html! {
220 <Comp />
221 };
222
223 html! {
224 <Comp field_1=1 />
225 };
226
227 html! {
228 <Comp field_2=2 />
229 };
230
231 html! {
232 <Comp field_1=1 field_2=2 />
233 };
234
235 let props = Props {
236 field_1: 1,
237 field_2: 1,
238 };
239
240 html! {
241 <Comp ..props />
242 };
243 }
244
245 #[test]
246 fn set_component_key() {
247 let test_key: Key = "test".to_string().into();
248 let check_key = |vnode: VNode| {
249 assert_eq!(vnode.key(), Some(&test_key));
250 };
251
252 let props = Props {
253 field_1: 1,
254 field_2: 1,
255 };
256 let props_2 = props.clone();
257
258 check_key(html! { <Comp key={test_key.clone()} /> });
259 check_key(html! { <Comp key={test_key.clone()} field_1=1 /> });
260 check_key(html! { <Comp field_1=1 key={test_key.clone()} /> });
261 check_key(html! { <Comp key={test_key.clone()} ..props /> });
262 check_key(html! { <Comp key={test_key.clone()} ..props_2 /> });
263 }
264
265 #[test]
266 fn vchild_partialeq() {
267 let vchild1: VChild<Comp> = VChild::new(
268 Props {
269 field_1: 1,
270 field_2: 1,
271 },
272 None,
273 );
274
275 let vchild2: VChild<Comp> = VChild::new(
276 Props {
277 field_1: 1,
278 field_2: 1,
279 },
280 None,
281 );
282
283 let vchild3: VChild<Comp> = VChild::new(
284 Props {
285 field_1: 2,
286 field_2: 2,
287 },
288 None,
289 );
290
291 assert_eq!(vchild1, vchild2);
292 assert_ne!(vchild1, vchild3);
293 assert_ne!(vchild2, vchild3);
294 }
295
296 #[derive(Clone, Properties, PartialEq)]
297 pub struct ListProps {
298 pub children: Children,
299 }
300 pub struct List;
301 impl Component for List {
302 type Message = ();
303 type Properties = ListProps;
304
305 fn create(_: &Context<Self>) -> Self {
306 Self
307 }
308
309 fn update(&mut self, _ctx: &Context<Self>, _: Self::Message) -> bool {
310 unimplemented!();
311 }
312
313 fn changed(&mut self, _ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
314 unimplemented!();
315 }
316
317 fn view(&self, ctx: &Context<Self>) -> Html {
318 let item_iter = ctx
319 .props()
320 .children
321 .iter()
322 .map(|item| html! {<li>{ item }</li>});
323 html! {
324 <ul>{ for item_iter }</ul>
325 }
326 }
327 }
328
329 fn setup_parent() -> (BSubtree, AnyScope, Element) {
330 let scope = AnyScope::test();
331 let parent = document().create_element("div").unwrap();
332 let root = BSubtree::create_root(&parent);
333
334 document().body().unwrap().append_child(&parent).unwrap();
335
336 (root, scope, parent)
337 }
338
339 fn get_html(node: Html, root: &BSubtree, scope: &AnyScope, parent: &Element) -> String {
340 parent.set_inner_html("");
342
343 node.attach(root, scope, parent, DomSlot::at_end());
344 scheduler::start_now();
345 parent.inner_html()
346 }
347
348 #[test]
349 fn all_ways_of_passing_children_work() {
350 let (root, scope, parent) = setup_parent();
351
352 let children: Vec<_> = vec!["a", "b", "c"]
353 .drain(..)
354 .map(|text| html! {<span>{ text }</span>})
355 .collect();
356 let children_renderer = Children::new(children.clone());
357 let expected_html = "\
358 <ul><li><span>a</span></li><li><span>b</span></li><li><span>c</span></li></ul>";
359
360 let prop_method = html! {
361 <List children={children_renderer.clone()} />
362 };
363 assert_eq!(get_html(prop_method, &root, &scope, &parent), expected_html);
364
365 let children_renderer_method = html! {
366 <List>
367 { children_renderer }
368 </List>
369 };
370 assert_eq!(
371 get_html(children_renderer_method, &root, &scope, &parent),
372 expected_html
373 );
374
375 let direct_method = html! {
376 <List>
377 { children.clone() }
378 </List>
379 };
380 assert_eq!(
381 get_html(direct_method, &root, &scope, &parent),
382 expected_html
383 );
384
385 let for_method = html! {
386 <List>
387 { for children }
388 </List>
389 };
390 assert_eq!(get_html(for_method, &root, &scope, &parent), expected_html);
391 }
392}
393
394#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
395#[cfg(test)]
396mod layout_tests {
397 extern crate self as yew;
398
399 use std::marker::PhantomData;
400
401 use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
402
403 use crate::tests::layout_tests::{diff_layouts, TestLayout};
404 use crate::{html, Children, Component, Context, Html, Properties};
405
406 wasm_bindgen_test_configure!(run_in_browser);
407
408 struct Comp<T> {
409 _marker: PhantomData<T>,
410 }
411
412 #[derive(Properties, Clone, PartialEq)]
413 struct CompProps {
414 #[prop_or_default]
415 children: Children,
416 }
417
418 impl<T: 'static> Component for Comp<T> {
419 type Message = ();
420 type Properties = CompProps;
421
422 fn create(_: &Context<Self>) -> Self {
423 Comp {
424 _marker: PhantomData::default(),
425 }
426 }
427
428 fn update(&mut self, _ctx: &Context<Self>, _: Self::Message) -> bool {
429 unimplemented!();
430 }
431
432 fn view(&self, ctx: &Context<Self>) -> Html {
433 html! {
434 <>{ ctx.props().children.clone() }</>
435 }
436 }
437 }
438
439 struct A;
440 struct B;
441
442 #[test]
443 fn diff() {
444 let layout1 = TestLayout {
445 name: "1",
446 node: html! {
447 <Comp<A>>
448 <Comp<B>></Comp<B>>
449 {"C"}
450 </Comp<A>>
451 },
452 expected: "C",
453 };
454
455 let layout2 = TestLayout {
456 name: "2",
457 node: html! {
458 <Comp<A>>
459 {"A"}
460 </Comp<A>>
461 },
462 expected: "A",
463 };
464
465 let layout3 = TestLayout {
466 name: "3",
467 node: html! {
468 <Comp<B>>
469 <Comp<A>></Comp<A>>
470 {"B"}
471 </Comp<B>>
472 },
473 expected: "B",
474 };
475
476 let layout4 = TestLayout {
477 name: "4",
478 node: html! {
479 <Comp<B>>
480 <Comp<A>>{"A"}</Comp<A>>
481 {"B"}
482 </Comp<B>>
483 },
484 expected: "AB",
485 };
486
487 let layout5 = TestLayout {
488 name: "5",
489 node: html! {
490 <Comp<B>>
491 <>
492 <Comp<A>>
493 {"A"}
494 </Comp<A>>
495 </>
496 {"B"}
497 </Comp<B>>
498 },
499 expected: "AB",
500 };
501
502 let layout6 = TestLayout {
503 name: "6",
504 node: html! {
505 <Comp<B>>
506 <>
507 <Comp<A>>
508 {"A"}
509 </Comp<A>>
510 {"B"}
511 </>
512 {"C"}
513 </Comp<B>>
514 },
515 expected: "ABC",
516 };
517
518 let layout7 = TestLayout {
519 name: "7",
520 node: html! {
521 <Comp<B>>
522 <>
523 <Comp<A>>
524 {"A"}
525 </Comp<A>>
526 <Comp<A>>
527 {"B"}
528 </Comp<A>>
529 </>
530 {"C"}
531 </Comp<B>>
532 },
533 expected: "ABC",
534 };
535
536 let layout8 = TestLayout {
537 name: "8",
538 node: html! {
539 <Comp<B>>
540 <>
541 <Comp<A>>
542 {"A"}
543 </Comp<A>>
544 <Comp<A>>
545 <Comp<A>>
546 {"B"}
547 </Comp<A>>
548 </Comp<A>>
549 </>
550 {"C"}
551 </Comp<B>>
552 },
553 expected: "ABC",
554 };
555
556 let layout9 = TestLayout {
557 name: "9",
558 node: html! {
559 <Comp<B>>
560 <>
561 <>
562 {"A"}
563 </>
564 <Comp<A>>
565 <Comp<A>>
566 {"B"}
567 </Comp<A>>
568 </Comp<A>>
569 </>
570 {"C"}
571 </Comp<B>>
572 },
573 expected: "ABC",
574 };
575
576 let layout10 = TestLayout {
577 name: "10",
578 node: html! {
579 <Comp<B>>
580 <>
581 <Comp<A>>
582 <Comp<A>>
583 {"A"}
584 </Comp<A>>
585 </Comp<A>>
586 <>
587 {"B"}
588 </>
589 </>
590 {"C"}
591 </Comp<B>>
592 },
593 expected: "ABC",
594 };
595
596 let layout11 = TestLayout {
597 name: "11",
598 node: html! {
599 <Comp<B>>
600 <>
601 <>
602 <Comp<A>>
603 <Comp<A>>
604 {"A"}
605 </Comp<A>>
606 {"B"}
607 </Comp<A>>
608 </>
609 </>
610 {"C"}
611 </Comp<B>>
612 },
613 expected: "ABC",
614 };
615
616 let layout12 = TestLayout {
617 name: "12",
618 node: html! {
619 <Comp<B>>
620 <>
621 <Comp<A>></Comp<A>>
622 <>
623 <Comp<A>>
624 <>
625 <Comp<A>>
626 {"A"}
627 </Comp<A>>
628 <></>
629 <Comp<A>>
630 <Comp<A>></Comp<A>>
631 <></>
632 {"B"}
633 <></>
634 <Comp<A>></Comp<A>>
635 </Comp<A>>
636 </>
637 </Comp<A>>
638 <></>
639 </>
640 <Comp<A>></Comp<A>>
641 </>
642 {"C"}
643 <Comp<A>></Comp<A>>
644 <></>
645 </Comp<B>>
646 },
647 expected: "ABC",
648 };
649
650 diff_layouts(vec![
651 layout1, layout2, layout3, layout4, layout5, layout6, layout7, layout8, layout9,
652 layout10, layout11, layout12,
653 ]);
654 }
655
656 #[test]
657 fn component_with_children() {
658 #[derive(Properties, PartialEq)]
659 struct Props {
660 children: Children,
661 }
662
663 struct ComponentWithChildren;
664
665 impl Component for ComponentWithChildren {
666 type Message = ();
667 type Properties = Props;
668
669 fn create(_ctx: &Context<Self>) -> Self {
670 Self
671 }
672
673 fn view(&self, ctx: &Context<Self>) -> Html {
674 html! {
675 <ul>
676 { for ctx.props().children.iter().map(|child| html! { <li>{ child }</li> }) }
677 </ul>
678 }
679 }
680 }
681
682 let layout = TestLayout {
683 name: "13",
684 node: html! {
685 <ComponentWithChildren>
686 if true {
687 <span>{ "hello" }</span>
688 <span>{ "world" }</span>
689 } else {
690 <span>{ "goodbye" }</span>
691 <span>{ "world" }</span>
692 }
693 </ComponentWithChildren>
694 },
695 expected: "<ul><li><span>hello</span><span>world</span></li></ul>",
696 };
697
698 diff_layouts(vec![layout]);
699 }
700}