1use std::any::Any;
4use std::rc::Rc;
5
6#[cfg(feature = "csr")]
7use web_sys::Element;
8
9use super::scope::{AnyScope, Scope};
10use super::BaseComponent;
11#[cfg(feature = "hydration")]
12use crate::dom_bundle::Fragment;
13#[cfg(feature = "csr")]
14use crate::dom_bundle::{BSubtree, Bundle, DomSlot, DynamicDomSlot};
15#[cfg(feature = "hydration")]
16use crate::html::RenderMode;
17use crate::html::{Html, RenderError};
18use crate::scheduler::{self, Runnable, Shared};
19use crate::suspense::{BaseSuspense, Suspension};
20use crate::{Callback, Context, HtmlResult};
21
22pub(crate) enum ComponentRenderState {
23 #[cfg(feature = "csr")]
24 Render {
25 bundle: Bundle,
26 root: BSubtree,
27 parent: Element,
28 sibling_slot: DynamicDomSlot,
30 own_slot: DynamicDomSlot,
33 },
34 #[cfg(feature = "hydration")]
35 Hydration {
36 fragment: Fragment,
37 root: BSubtree,
38 parent: Element,
39 sibling_slot: DynamicDomSlot,
40 own_slot: DynamicDomSlot,
41 },
42 #[cfg(feature = "ssr")]
43 Ssr {
44 sender: Option<crate::platform::pinned::oneshot::Sender<Html>>,
45 },
46}
47
48impl std::fmt::Debug for ComponentRenderState {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 match self {
51 #[cfg(feature = "csr")]
52 Self::Render {
53 ref bundle,
54 root,
55 ref parent,
56 ref sibling_slot,
57 ref own_slot,
58 } => f
59 .debug_struct("ComponentRenderState::Render")
60 .field("bundle", bundle)
61 .field("root", root)
62 .field("parent", parent)
63 .field("sibling_slot", sibling_slot)
64 .field("own_slot", own_slot)
65 .finish(),
66
67 #[cfg(feature = "hydration")]
68 Self::Hydration {
69 ref fragment,
70 ref parent,
71 ref sibling_slot,
72 ref own_slot,
73 ref root,
74 } => f
75 .debug_struct("ComponentRenderState::Hydration")
76 .field("fragment", fragment)
77 .field("root", root)
78 .field("parent", parent)
79 .field("sibling_slot", sibling_slot)
80 .field("own_slot", own_slot)
81 .finish(),
82
83 #[cfg(feature = "ssr")]
84 Self::Ssr { ref sender } => {
85 let sender_repr = match sender {
86 Some(_) => "Some(_)",
87 None => "None",
88 };
89
90 f.debug_struct("ComponentRenderState::Ssr")
91 .field("sender", &sender_repr)
92 .finish()
93 }
94 }
95 }
96}
97
98#[cfg(feature = "csr")]
99impl ComponentRenderState {
100 pub(crate) fn shift(&mut self, next_parent: Element, next_slot: DomSlot) {
101 match self {
102 #[cfg(feature = "csr")]
103 Self::Render {
104 bundle,
105 parent,
106 sibling_slot,
107 ..
108 } => {
109 bundle.shift(&next_parent, next_slot.clone());
110
111 *parent = next_parent;
112 sibling_slot.reassign(next_slot);
113 }
114 #[cfg(feature = "hydration")]
115 Self::Hydration {
116 fragment,
117 parent,
118 sibling_slot,
119 ..
120 } => {
121 fragment.shift(&next_parent, next_slot.clone());
122
123 *parent = next_parent;
124 sibling_slot.reassign(next_slot);
125 }
126
127 #[cfg(feature = "ssr")]
128 Self::Ssr { .. } => {
129 #[cfg(debug_assertions)]
130 panic!("shifting is not possible during SSR");
131 }
132 }
133 }
134}
135
136struct CompStateInner<COMP>
137where
138 COMP: BaseComponent,
139{
140 pub(crate) component: COMP,
141 pub(crate) context: Context<COMP>,
142}
143
144pub(crate) trait Stateful {
150 fn view(&self) -> HtmlResult;
151 #[cfg(feature = "csr")]
152 fn rendered(&mut self, first_render: bool);
153 fn destroy(&mut self);
154
155 fn any_scope(&self) -> AnyScope;
156
157 fn flush_messages(&mut self) -> bool;
158 #[cfg(feature = "csr")]
159 fn props_changed(&mut self, props: Rc<dyn Any>) -> bool;
160
161 fn as_any(&self) -> &dyn Any;
162
163 #[cfg(feature = "hydration")]
164 fn creation_mode(&self) -> RenderMode;
165}
166
167impl<COMP> Stateful for CompStateInner<COMP>
168where
169 COMP: BaseComponent,
170{
171 fn view(&self) -> HtmlResult {
172 self.component.view(&self.context)
173 }
174
175 #[cfg(feature = "csr")]
176 fn rendered(&mut self, first_render: bool) {
177 self.component.rendered(&self.context, first_render)
178 }
179
180 fn destroy(&mut self) {
181 self.component.destroy(&self.context);
182 }
183
184 fn any_scope(&self) -> AnyScope {
185 self.context.link().clone().into()
186 }
187
188 #[cfg(feature = "hydration")]
189 fn creation_mode(&self) -> RenderMode {
190 self.context.creation_mode()
191 }
192
193 fn flush_messages(&mut self) -> bool {
194 self.context
195 .link()
196 .pending_messages
197 .drain()
198 .into_iter()
199 .fold(false, |acc, msg| {
200 self.component.update(&self.context, msg) || acc
201 })
202 }
203
204 #[cfg(feature = "csr")]
205 fn props_changed(&mut self, props: Rc<dyn Any>) -> bool {
206 let props = match Rc::downcast::<COMP::Properties>(props) {
207 Ok(m) => m,
208 _ => return false,
209 };
210
211 if self.context.props != props {
212 let old_props = std::mem::replace(&mut self.context.props, props);
213 self.component.changed(&self.context, &old_props)
214 } else {
215 false
216 }
217 }
218
219 fn as_any(&self) -> &dyn Any {
220 self
221 }
222}
223
224pub(crate) struct ComponentState {
225 pub(super) inner: Box<dyn Stateful>,
226
227 pub(super) render_state: ComponentRenderState,
228
229 #[cfg(feature = "csr")]
230 has_rendered: bool,
231 #[cfg(feature = "hydration")]
232 pending_props: Option<Rc<dyn Any>>,
233
234 suspension: Option<Suspension>,
235
236 pub(crate) comp_id: usize,
237}
238
239impl ComponentState {
240 #[tracing::instrument(
241 level = tracing::Level::DEBUG,
242 name = "create",
243 skip_all,
244 fields(component.id = scope.id),
245 )]
246 fn new<COMP: BaseComponent>(
247 initial_render_state: ComponentRenderState,
248 scope: Scope<COMP>,
249 props: Rc<COMP::Properties>,
250 #[cfg(feature = "hydration")] prepared_state: Option<String>,
251 ) -> Self {
252 let comp_id = scope.id;
253 #[cfg(feature = "hydration")]
254 let creation_mode = {
255 match initial_render_state {
256 ComponentRenderState::Render { .. } => RenderMode::Render,
257 ComponentRenderState::Hydration { .. } => RenderMode::Hydration,
258 #[cfg(feature = "ssr")]
259 ComponentRenderState::Ssr { .. } => RenderMode::Ssr,
260 }
261 };
262
263 let context = Context {
264 scope,
265 props,
266 #[cfg(feature = "hydration")]
267 creation_mode,
268 #[cfg(feature = "hydration")]
269 prepared_state,
270 };
271
272 let inner = Box::new(CompStateInner {
273 component: COMP::create(&context),
274 context,
275 });
276
277 Self {
278 inner,
279 render_state: initial_render_state,
280 suspension: None,
281
282 #[cfg(feature = "csr")]
283 has_rendered: false,
284 #[cfg(feature = "hydration")]
285 pending_props: None,
286
287 comp_id,
288 }
289 }
290
291 pub(crate) fn downcast_comp_ref<COMP>(&self) -> Option<&COMP>
292 where
293 COMP: BaseComponent + 'static,
294 {
295 self.inner
296 .as_any()
297 .downcast_ref::<CompStateInner<COMP>>()
298 .map(|m| &m.component)
299 }
300
301 fn resume_existing_suspension(&mut self) {
302 if let Some(m) = self.suspension.take() {
303 let comp_scope = self.inner.any_scope();
304
305 let suspense_scope = comp_scope.find_parent_scope::<BaseSuspense>().unwrap();
306 BaseSuspense::resume(&suspense_scope, m);
307 }
308 }
309}
310
311pub(crate) struct CreateRunner<COMP: BaseComponent> {
312 pub initial_render_state: ComponentRenderState,
313 pub props: Rc<COMP::Properties>,
314 pub scope: Scope<COMP>,
315 #[cfg(feature = "hydration")]
316 pub prepared_state: Option<String>,
317}
318
319impl<COMP: BaseComponent> Runnable for CreateRunner<COMP> {
320 fn run(self: Box<Self>) {
321 let mut current_state = self.scope.state.borrow_mut();
322 if current_state.is_none() {
323 *current_state = Some(ComponentState::new(
324 self.initial_render_state,
325 self.scope.clone(),
326 self.props,
327 #[cfg(feature = "hydration")]
328 self.prepared_state,
329 ));
330 }
331 }
332}
333
334pub(crate) struct UpdateRunner {
335 pub state: Shared<Option<ComponentState>>,
336}
337
338impl ComponentState {
339 #[tracing::instrument(
340 level = tracing::Level::DEBUG,
341 skip(self),
342 fields(component.id = self.comp_id)
343 )]
344 fn update(&mut self) -> bool {
345 let schedule_render = self.inner.flush_messages();
346 tracing::trace!(schedule_render);
347 schedule_render
348 }
349}
350
351impl Runnable for UpdateRunner {
352 fn run(self: Box<Self>) {
353 if let Some(state) = self.state.borrow_mut().as_mut() {
354 let schedule_render = state.update();
355
356 if schedule_render {
357 scheduler::push_component_render(
358 state.comp_id,
359 Box::new(RenderRunner {
360 state: self.state.clone(),
361 }),
362 );
363 }
365 }
366 }
367}
368
369pub(crate) struct DestroyRunner {
370 pub state: Shared<Option<ComponentState>>,
371 pub parent_to_detach: bool,
372}
373
374impl ComponentState {
375 #[tracing::instrument(
376 level = tracing::Level::DEBUG,
377 skip(self),
378 fields(component.id = self.comp_id)
379 )]
380 fn destroy(mut self, parent_to_detach: bool) {
381 self.inner.destroy();
382 self.resume_existing_suspension();
383
384 match self.render_state {
385 #[cfg(feature = "csr")]
386 ComponentRenderState::Render {
387 bundle,
388 ref parent,
389 ref root,
390 ..
391 } => {
392 bundle.detach(root, parent, parent_to_detach);
393 }
394 #[cfg(feature = "hydration")]
396 ComponentRenderState::Hydration {
397 ref root,
398 fragment,
399 ref parent,
400 ..
401 } => {
402 fragment.detach(root, parent, parent_to_detach);
403 }
404
405 #[cfg(feature = "ssr")]
406 ComponentRenderState::Ssr { .. } => {
407 let _ = parent_to_detach;
408 }
409 }
410 }
411}
412
413impl Runnable for DestroyRunner {
414 fn run(self: Box<Self>) {
415 if let Some(state) = self.state.borrow_mut().take() {
416 state.destroy(self.parent_to_detach);
417 }
418 }
419}
420
421pub(crate) struct RenderRunner {
422 pub state: Shared<Option<ComponentState>>,
423}
424
425impl ComponentState {
426 #[tracing::instrument(
427 level = tracing::Level::DEBUG,
428 skip_all,
429 fields(component.id = self.comp_id)
430 )]
431 fn render(&mut self, shared_state: &Shared<Option<ComponentState>>) {
432 let view = self.inner.view();
433 tracing::trace!(?view, "render result");
434 match view {
435 Ok(vnode) => self.commit_render(shared_state, vnode),
436 Err(RenderError::Suspended(susp)) => self.suspend(shared_state, susp),
437 };
438 }
439
440 fn suspend(&mut self, shared_state: &Shared<Option<ComponentState>>, suspension: Suspension) {
441 if suspension.resumed() {
445 scheduler::push_component_render(
447 self.comp_id,
448 Box::new(RenderRunner {
449 state: shared_state.clone(),
450 }),
451 );
452 } else {
453 let comp_scope = self.inner.any_scope();
455
456 let suspense_scope = comp_scope
457 .find_parent_scope::<BaseSuspense>()
458 .expect("To suspend rendering, a <Suspense /> component is required.");
459
460 let comp_id = self.comp_id;
461 let shared_state = shared_state.clone();
462 suspension.listen(Callback::from(move |_| {
463 scheduler::push_component_render(
464 comp_id,
465 Box::new(RenderRunner {
466 state: shared_state.clone(),
467 }),
468 );
469 scheduler::start();
470 }));
471
472 if let Some(ref last_suspension) = self.suspension {
473 if &suspension != last_suspension {
474 BaseSuspense::resume(&suspense_scope, last_suspension.clone());
476 }
477 }
478 self.suspension = Some(suspension.clone());
479
480 BaseSuspense::suspend(&suspense_scope, suspension);
481 }
482 }
483
484 fn commit_render(&mut self, shared_state: &Shared<Option<ComponentState>>, new_root: Html) {
485 self.resume_existing_suspension();
488
489 match self.render_state {
490 #[cfg(feature = "csr")]
491 ComponentRenderState::Render {
492 ref mut bundle,
493 ref parent,
494 ref root,
495 ref sibling_slot,
496 ref mut own_slot,
497 ..
498 } => {
499 let scope = self.inner.any_scope();
500
501 let new_node_ref =
502 bundle.reconcile(root, &scope, parent, sibling_slot.to_position(), new_root);
503 own_slot.reassign(new_node_ref);
504
505 let first_render = !self.has_rendered;
506 self.has_rendered = true;
507
508 scheduler::push_component_rendered(
509 self.comp_id,
510 Box::new(RenderedRunner {
511 state: shared_state.clone(),
512 first_render,
513 }),
514 first_render,
515 );
516 }
517
518 #[cfg(feature = "hydration")]
519 ComponentRenderState::Hydration {
520 ref mut fragment,
521 ref parent,
522 ref mut own_slot,
523 ref mut sibling_slot,
524 ref root,
525 } => {
526 scheduler::push_component_priority_render(
529 self.comp_id,
530 Box::new(RenderRunner {
531 state: shared_state.clone(),
532 }),
533 );
534
535 let scope = self.inner.any_scope();
536
537 let bundle = Bundle::hydrate(root, &scope, parent, fragment, new_root);
541
542 fragment.trim_start_text_nodes();
544
545 assert!(fragment.is_empty(), "expected end of component, found node");
546
547 self.render_state = ComponentRenderState::Render {
548 root: root.clone(),
549 bundle,
550 parent: parent.clone(),
551 own_slot: std::mem::replace(own_slot, DynamicDomSlot::new_debug_trapped()),
552 sibling_slot: std::mem::replace(
553 sibling_slot,
554 DynamicDomSlot::new_debug_trapped(),
555 ),
556 };
557 }
558
559 #[cfg(feature = "ssr")]
560 ComponentRenderState::Ssr { ref mut sender } => {
561 let _ = shared_state;
562 if let Some(tx) = sender.take() {
563 tx.send(new_root).unwrap();
564 }
565 }
566 };
567 }
568}
569
570impl Runnable for RenderRunner {
571 fn run(self: Box<Self>) {
572 let mut state = self.state.borrow_mut();
573 let state = match state.as_mut() {
574 None => return, Some(state) => state,
576 };
577
578 state.render(&self.state);
579 }
580}
581
582#[cfg(feature = "csr")]
583mod feat_csr {
584 use super::*;
585
586 pub(crate) struct PropsUpdateRunner {
587 pub state: Shared<Option<ComponentState>>,
588 pub props: Option<Rc<dyn Any>>,
589 pub next_sibling_slot: Option<DomSlot>,
590 }
591
592 impl ComponentState {
593 #[tracing::instrument(
594 level = tracing::Level::DEBUG,
595 skip(self),
596 fields(component.id = self.comp_id)
597 )]
598 fn changed(
599 &mut self,
600 props: Option<Rc<dyn Any>>,
601 next_sibling_slot: Option<DomSlot>,
602 ) -> bool {
603 if let Some(next_sibling_slot) = next_sibling_slot {
604 match &mut self.render_state {
608 #[cfg(feature = "csr")]
609 ComponentRenderState::Render { sibling_slot, .. } => {
610 sibling_slot.reassign(next_sibling_slot);
611 }
612
613 #[cfg(feature = "hydration")]
614 ComponentRenderState::Hydration { sibling_slot, .. } => {
615 sibling_slot.reassign(next_sibling_slot);
616 }
617
618 #[cfg(feature = "ssr")]
619 ComponentRenderState::Ssr { .. } => {
620 #[cfg(debug_assertions)]
621 panic!("properties do not change during SSR");
622 }
623 }
624 }
625
626 let should_render = |props: Option<Rc<dyn Any>>, state: &mut ComponentState| -> bool {
627 props.map(|m| state.inner.props_changed(m)).unwrap_or(false)
628 };
629
630 #[cfg(feature = "hydration")]
631 let should_render_hydration =
632 |props: Option<Rc<dyn Any>>, state: &mut ComponentState| -> bool {
633 if let Some(props) = props.or_else(|| state.pending_props.take()) {
634 match state.has_rendered {
635 true => {
636 state.pending_props = None;
637 state.inner.props_changed(props)
638 }
639 false => {
640 state.pending_props = Some(props);
641 false
642 }
643 }
644 } else {
645 false
646 }
647 };
648
649 let schedule_render = {
651 #[cfg(feature = "hydration")]
652 {
653 if self.inner.creation_mode() == RenderMode::Hydration {
654 should_render_hydration(props, self)
655 } else {
656 should_render(props, self)
657 }
658 }
659
660 #[cfg(not(feature = "hydration"))]
661 should_render(props, self)
662 };
663
664 tracing::trace!(
665 "props_update(has_rendered={} schedule_render={})",
666 self.has_rendered,
667 schedule_render
668 );
669 schedule_render
670 }
671 }
672
673 impl Runnable for PropsUpdateRunner {
674 fn run(self: Box<Self>) {
675 let Self {
676 next_sibling_slot,
677 props,
678 state: shared_state,
679 } = *self;
680
681 if let Some(state) = shared_state.borrow_mut().as_mut() {
682 let schedule_render = state.changed(props, next_sibling_slot);
683
684 if schedule_render {
685 scheduler::push_component_render(
686 state.comp_id,
687 Box::new(RenderRunner {
688 state: shared_state.clone(),
689 }),
690 );
691 }
693 };
694 }
695 }
696
697 pub(crate) struct RenderedRunner {
698 pub state: Shared<Option<ComponentState>>,
699 pub first_render: bool,
700 }
701
702 impl ComponentState {
703 #[tracing::instrument(
704 level = tracing::Level::DEBUG,
705 skip(self),
706 fields(component.id = self.comp_id)
707 )]
708 fn rendered(&mut self, first_render: bool) -> bool {
709 if self.suspension.is_none() {
710 self.inner.rendered(first_render);
711 }
712
713 #[cfg(feature = "hydration")]
714 {
715 self.pending_props.is_some()
716 }
717 #[cfg(not(feature = "hydration"))]
718 {
719 false
720 }
721 }
722 }
723
724 impl Runnable for RenderedRunner {
725 fn run(self: Box<Self>) {
726 if let Some(state) = self.state.borrow_mut().as_mut() {
727 let has_pending_props = state.rendered(self.first_render);
728
729 if has_pending_props {
730 scheduler::push_component_props_update(Box::new(PropsUpdateRunner {
731 state: self.state.clone(),
732 props: None,
733 next_sibling_slot: None,
734 }));
735 }
736 }
737 }
738 }
739}
740
741#[cfg(feature = "csr")]
742pub(super) use feat_csr::*;
743
744#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
745#[cfg(test)]
746mod tests {
747 extern crate self as yew;
748
749 use std::cell::RefCell;
750 use std::ops::Deref;
751 use std::rc::Rc;
752
753 use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
754
755 use super::*;
756 use crate::dom_bundle::BSubtree;
757 use crate::html::*;
758 use crate::{html, Properties};
759
760 wasm_bindgen_test_configure!(run_in_browser);
761
762 #[derive(Clone, Properties, Default, PartialEq)]
763 struct ChildProps {
764 lifecycle: Rc<RefCell<Vec<String>>>,
765 }
766
767 struct Child {}
768
769 impl Component for Child {
770 type Message = ();
771 type Properties = ChildProps;
772
773 fn create(_ctx: &Context<Self>) -> Self {
774 Child {}
775 }
776
777 fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
778 ctx.props()
779 .lifecycle
780 .borrow_mut()
781 .push("child rendered".into());
782 }
783
784 fn update(&mut self, _ctx: &Context<Self>, _: Self::Message) -> bool {
785 false
786 }
787
788 fn changed(&mut self, _ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
789 false
790 }
791
792 fn view(&self, _ctx: &Context<Self>) -> Html {
793 html! {}
794 }
795 }
796
797 #[derive(Clone, Properties, Default, PartialEq)]
798 struct Props {
799 lifecycle: Rc<RefCell<Vec<String>>>,
800 #[allow(dead_code)]
801 #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
802 create_message: Option<bool>,
803 update_message: RefCell<Option<bool>>,
804 view_message: RefCell<Option<bool>>,
805 rendered_message: RefCell<Option<bool>>,
806 }
807
808 struct Comp {
809 lifecycle: Rc<RefCell<Vec<String>>>,
810 }
811
812 impl Component for Comp {
813 type Message = bool;
814 type Properties = Props;
815
816 fn create(ctx: &Context<Self>) -> Self {
817 ctx.props().lifecycle.borrow_mut().push("create".into());
818 #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
819 if let Some(msg) = ctx.props().create_message {
820 ctx.link().send_message(msg);
821 }
822 Comp {
823 lifecycle: Rc::clone(&ctx.props().lifecycle),
824 }
825 }
826
827 fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
828 if let Some(msg) = ctx.props().rendered_message.borrow_mut().take() {
829 ctx.link().send_message(msg);
830 }
831 ctx.props()
832 .lifecycle
833 .borrow_mut()
834 .push(format!("rendered({})", first_render));
835 }
836
837 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
838 if let Some(msg) = ctx.props().update_message.borrow_mut().take() {
839 ctx.link().send_message(msg);
840 }
841 ctx.props()
842 .lifecycle
843 .borrow_mut()
844 .push(format!("update({})", msg));
845 msg
846 }
847
848 fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
849 self.lifecycle = Rc::clone(&ctx.props().lifecycle);
850 self.lifecycle.borrow_mut().push("change".into());
851 false
852 }
853
854 fn view(&self, ctx: &Context<Self>) -> Html {
855 if let Some(msg) = ctx.props().view_message.borrow_mut().take() {
856 ctx.link().send_message(msg);
857 }
858 self.lifecycle.borrow_mut().push("view".into());
859 html! { <Child lifecycle={self.lifecycle.clone()} /> }
860 }
861 }
862
863 impl Drop for Comp {
864 fn drop(&mut self) {
865 self.lifecycle.borrow_mut().push("drop".into());
866 }
867 }
868
869 fn test_lifecycle(props: Props, expected: &[&str]) {
870 let document = gloo::utils::document();
871 let scope = Scope::<Comp>::new(None);
872 let parent = document.create_element("div").unwrap();
873 let root = BSubtree::create_root(&parent);
874
875 let lifecycle = props.lifecycle.clone();
876
877 lifecycle.borrow_mut().clear();
878 scope.mount_in_place(
879 root,
880 parent,
881 DomSlot::at_end(),
882 DynamicDomSlot::new_debug_trapped(),
883 Rc::new(props),
884 );
885 crate::scheduler::start_now();
886
887 assert_eq!(&lifecycle.borrow_mut().deref()[..], expected);
888 }
889
890 #[test]
891 fn lifecycle_tests() {
892 let lifecycle: Rc<RefCell<Vec<String>>> = Rc::default();
893
894 test_lifecycle(
895 Props {
896 lifecycle: lifecycle.clone(),
897 ..Props::default()
898 },
899 &["create", "view", "child rendered", "rendered(true)"],
900 );
901
902 test_lifecycle(
903 Props {
904 lifecycle: lifecycle.clone(),
905 #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
906 create_message: Some(false),
907 ..Props::default()
908 },
909 &[
910 "create",
911 "view",
912 "child rendered",
913 "rendered(true)",
914 "update(false)",
915 ],
916 );
917
918 test_lifecycle(
919 Props {
920 lifecycle: lifecycle.clone(),
921 view_message: RefCell::new(Some(true)),
922 ..Props::default()
923 },
924 &[
925 "create",
926 "view",
927 "child rendered",
928 "rendered(true)",
929 "update(true)",
930 "view",
931 "rendered(false)",
932 ],
933 );
934
935 test_lifecycle(
936 Props {
937 lifecycle: lifecycle.clone(),
938 view_message: RefCell::new(Some(false)),
939 ..Props::default()
940 },
941 &[
942 "create",
943 "view",
944 "child rendered",
945 "rendered(true)",
946 "update(false)",
947 ],
948 );
949
950 test_lifecycle(
951 Props {
952 lifecycle: lifecycle.clone(),
953 rendered_message: RefCell::new(Some(false)),
954 ..Props::default()
955 },
956 &[
957 "create",
958 "view",
959 "child rendered",
960 "rendered(true)",
961 "update(false)",
962 ],
963 );
964
965 test_lifecycle(
966 Props {
967 lifecycle: lifecycle.clone(),
968 rendered_message: RefCell::new(Some(true)),
969 ..Props::default()
970 },
971 &[
972 "create",
973 "view",
974 "child rendered",
975 "rendered(true)",
976 "update(true)",
977 "view",
978 "rendered(false)",
979 ],
980 );
981
982 test_lifecycle(
984 Props {
985 lifecycle,
986 #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
987 create_message: Some(true),
988 update_message: RefCell::new(Some(true)),
989 ..Props::default()
990 },
991 &[
992 "create",
993 "view",
994 "child rendered",
995 "rendered(true)",
996 "update(true)",
997 "update(true)",
998 "view",
999 "rendered(false)",
1000 ],
1001 );
1002 }
1003}