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