#![allow(non_snake_case)] /* Stress Miri as much as possible. Prove that we don't leak memory and that our methods are safe. Specifically: - [ ] VirtualDom drops memory safely - [ ] Borrowed components don't expose invalid pointers - [ ] Async isn't busted */ use dioxus::prelude::*; /// This test ensures that if a component aborts early, it is replaced with a placeholder. /// In debug, this should also toss a warning. #[test] #[ignore] fn test_memory_leak() { fn app(cx: Scope) -> Element { let val = cx.use_hook(|| 0); *val += 1; if *val == 2 || *val == 4 { return cx.render(rsx!(())); } let name = cx.use_hook(|| String::from("asd")); cx.render(rsx!( div { "Hello, world!" } Child {} Child {} Child {} Child {} Child {} Child {} BorrowedChild { na: name } BorrowedChild { na: name } BorrowedChild { na: name } BorrowedChild { na: name } BorrowedChild { na: name } )) } #[derive(Props)] struct BorrowedProps<'a> { na: &'a str, } fn BorrowedChild<'a>(cx: Scope<'a, BorrowedProps<'a>>) -> Element { render!(div { "goodbye {cx.props.na}" Child {} Child {} }) } fn Child(cx: Scope) -> Element { render!(div { "goodbye world" }) } let mut dom = VirtualDom::new(app); dom.rebuild(); dom.hard_diff(ScopeId(0)); dom.hard_diff(ScopeId(0)); dom.hard_diff(ScopeId(0)); dom.hard_diff(ScopeId(0)); dom.hard_diff(ScopeId(0)); dom.hard_diff(ScopeId(0)); } #[test] fn memo_works_properly() { fn app(cx: Scope) -> Element { let val = cx.use_hook(|| 0); *val += 1; if *val == 2 || *val == 4 { return None; } let name = cx.use_hook(|| String::from("asd")); cx.render(rsx!( div { "Hello, world! {name}" } Child { na: "asdfg".to_string() } )) } #[derive(PartialEq, Props)] struct ChildProps { na: String, } fn Child(cx: Scope) -> Element { render!(div { "goodbye world" }) } let mut dom = new_dom(app, ()); dom.rebuild(); dom.hard_diff(ScopeId(0)); dom.hard_diff(ScopeId(0)); dom.hard_diff(ScopeId(0)); dom.hard_diff(ScopeId(0)); dom.hard_diff(ScopeId(0)); dom.hard_diff(ScopeId(0)); dom.hard_diff(ScopeId(0)); } #[test] fn free_works_on_root_props() { fn app(cx: Scope) -> Element { cx.render(rsx! { Child { a: "alpha"} Child { a: "beta"} Child { a: "gamma"} Child { a: "delta"} }) } #[derive(Props, PartialEq)] struct ChildProps { a: &'static str, } fn Child(cx: Scope) -> Element { render!("child {cx.props.a}") } struct Custom { val: String, } impl Drop for Custom { fn drop(&mut self) { dbg!("dropped! {}", &self.val); } } let mut dom = new_dom(app, Custom { val: String::from("asd") }); dom.rebuild(); } #[test] fn free_works_on_borrowed() { fn app(cx: Scope) -> Element { cx.render(rsx! { Child { a: "alpha", b: "asd".to_string() } }) } #[derive(Props)] struct ChildProps<'a> { a: &'a str, b: String, } fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element { dbg!("rendering child"); render!("child {cx.props.a}, {cx.props.b}") } impl Drop for ChildProps<'_> { fn drop(&mut self) { dbg!("dropped child!"); } } let mut dom = new_dom(app, ()); let _ = dom.rebuild(); } #[test] fn free_works_on_root_hooks() { /* On Drop, scopearena drops all the hook contents. */ struct Droppable(T); impl Drop for Droppable { fn drop(&mut self) { dbg!("dropping droppable"); } } fn app(cx: Scope) -> Element { let name = cx.use_hook(|| Droppable(String::from("asd"))); render!(div { "{name.0}" }) } let mut dom = new_dom(app, ()); let _ = dom.rebuild(); } #[test] fn old_props_arent_stale() { fn app(cx: Scope) -> Element { dbg!("rendering parent"); let cnt = cx.use_hook(|| 0); *cnt += 1; if *cnt == 1 { render!(div { Child { a: "abcdef".to_string() } }) } else { render!(div { Child { a: "abcdef".to_string() } }) } } #[derive(Props, PartialEq)] struct ChildProps { a: String, } fn Child(cx: Scope) -> Element { dbg!("rendering child", &cx.props.a); render!(div { "child {cx.props.a}" }) } let mut dom = new_dom(app, ()); let _ = dom.rebuild(); dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); dom.work_with_deadline(|| false); dbg!("forcing update to child"); dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); dom.work_with_deadline(|| false); } #[test] fn basic() { fn app(cx: Scope) -> Element { render!(div { Child { a: "abcdef".to_string() } }) } #[derive(Props, PartialEq)] struct ChildProps { a: String, } fn Child(cx: Scope) -> Element { dbg!("rendering child", &cx.props.a); render!(div { "child {cx.props.a}" }) } let mut dom = new_dom(app, ()); let _ = dom.rebuild(); dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); dom.work_with_deadline(|| false); } #[test] fn leak_thru_children() { fn app(cx: Scope) -> Element { cx.render(rsx! { Child { name: "asd".to_string(), } }); cx.render(rsx! { div {} }) } #[inline_props] fn Child(cx: Scope, name: String) -> Element { render!(div { "child {name}" }) } let mut dom = new_dom(app, ()); let _ = dom.rebuild(); dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); dom.work_with_deadline(|| false); } #[test] fn test_pass_thru() { #[inline_props] fn NavContainer<'a>(cx: Scope, children: Element<'a>) -> Element { cx.render(rsx! { header { nav { children } } }) } fn NavMenu(cx: Scope) -> Element { render!( NavBrand {} div { NavStart {} NavEnd {} } ) } fn NavBrand(cx: Scope) -> Element { render!(div {}) } fn NavStart(cx: Scope) -> Element { render!(div {}) } fn NavEnd(cx: Scope) -> Element { render!(div {}) } #[inline_props] fn MainContainer<'a>( cx: Scope, nav: Element<'a>, body: Element<'a>, footer: Element<'a>, ) -> Element { cx.render(rsx! { div { class: "columns is-mobile", div { class: "column is-full", nav, body, footer, } } }) } fn app(cx: Scope) -> Element { let nav = cx.render(rsx! { NavContainer { NavMenu {} } }); let body = cx.render(rsx! { div {} }); let footer = cx.render(rsx! { div {} }); cx.render(rsx! { MainContainer { nav: nav, body: body, footer: footer, } }) } let mut dom = new_dom(app, ()); let _ = dom.rebuild(); for _ in 0..40 { dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(0))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(1))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(2))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(2))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(2))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(3))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(3))); dom.work_with_deadline(|| false); dom.handle_message(SchedulerMsg::Immediate(ScopeId(3))); dom.work_with_deadline(|| false); } }