Fix flakey windows tests (#2332)

This commit is contained in:
Evan Almloff 2024-04-17 17:08:38 -05:00 committed by GitHub
parent c9ab09b348
commit 5ce91e1bfc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 217 additions and 136 deletions

3
Cargo.lock generated
View file

@ -2371,6 +2371,7 @@ dependencies = [
"base64 0.21.7", "base64 0.21.7",
"bytes", "bytes",
"ciborium", "ciborium",
"dioxus",
"dioxus-cli-config", "dioxus-cli-config",
"dioxus-desktop", "dioxus-desktop",
"dioxus-hot-reload", "dioxus-hot-reload",
@ -2488,6 +2489,7 @@ dependencies = [
name = "dioxus-lib" name = "dioxus-lib"
version = "0.5.2" version = "0.5.2"
dependencies = [ dependencies = [
"dioxus",
"dioxus-config-macro", "dioxus-config-macro",
"dioxus-core 0.5.2", "dioxus-core 0.5.2",
"dioxus-core-macro", "dioxus-core-macro",
@ -2603,6 +2605,7 @@ dependencies = [
name = "dioxus-router-macro" name = "dioxus-router-macro"
version = "0.5.2" version = "0.5.2"
dependencies = [ dependencies = [
"dioxus",
"proc-macro2", "proc-macro2",
"quote", "quote",
"slab", "slab",

View file

@ -88,7 +88,7 @@ command = "cargo"
args = ["build"] args = ["build"]
[tasks.test-flow] [tasks.test-flow]
dependencies = ["test"] dependencies = ["test", "docs"]
private = true private = true
[tasks.test] [tasks.test]
@ -101,8 +101,7 @@ args = [
"--tests", "--tests",
"--examples", "--examples",
"--workspace", "--workspace",
"--exclude", # These tests run on Ubuntu without a screen. Desktop tests that require a screen fail, so we skip them
"dioxus-router",
"--exclude", "--exclude",
"dioxus-desktop", "dioxus-desktop",
"--exclude", "--exclude",
@ -110,6 +109,16 @@ args = [
] ]
private = true private = true
[tasks.docs]
dependencies = ["build"]
command = "cargo"
args = [
"test",
"--doc",
"--workspace",
]
private = true
[tasks.test-with-browser] [tasks.test-with-browser]
env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = [ env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = [
"**/packages/router", "**/packages/router",

View file

@ -1,7 +1,6 @@
//! The typical TodoMVC app, implemented in Dioxus. //! The typical TodoMVC app, implemented in Dioxus.
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_elements::input_data::keyboard_types::Key;
use std::collections::HashMap; use std::collections::HashMap;
fn main() { fn main() {
@ -74,7 +73,7 @@ fn app() -> Element {
class: "toggle-all", class: "toggle-all",
r#type: "checkbox", r#type: "checkbox",
onchange: toggle_all, onchange: toggle_all,
checked: active_todo_count() == 0, checked: active_todo_count() == 0
} }
label { r#for: "toggle-all" } label { r#for: "toggle-all" }
} }
@ -98,8 +97,14 @@ fn app() -> Element {
// A simple info footer // A simple info footer
footer { class: "info", footer { class: "info",
p { "Double-click to edit a todo" } p { "Double-click to edit a todo" }
p { "Created by " a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" } } p {
p { "Part of " a { href: "http://todomvc.com", "TodoMVC" } } "Created by "
a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }
}
p {
"Part of "
a { href: "http://todomvc.com", "TodoMVC" }
}
} }
} }
} }
@ -132,7 +137,7 @@ fn TodoHeader(mut todos: Signal<HashMap<u32, TodoItem>>) -> Element {
value: "{draft}", value: "{draft}",
autofocus: "true", autofocus: "true",
oninput: move |evt| draft.set(evt.value()), oninput: move |evt| draft.set(evt.value()),
onkeydown, onkeydown
} }
} }
} }
@ -165,7 +170,7 @@ fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
r#type: "checkbox", r#type: "checkbox",
id: "cbg-{id}", id: "cbg-{id}",
checked: "{checked}", checked: "{checked}",
oninput: move |evt| todos.write().get_mut(&id).unwrap().checked = evt.checked(), oninput: move |evt| todos.write().get_mut(&id).unwrap().checked = evt.checked()
} }
label { label {
r#for: "cbg-{id}", r#for: "cbg-{id}",
@ -175,7 +180,9 @@ fn TodoEntry(mut todos: Signal<HashMap<u32, TodoItem>>, id: u32) -> Element {
} }
button { button {
class: "destroy", class: "destroy",
onclick: move |_| { todos.write().remove(&id); }, onclick: move |_| {
todos.write().remove(&id);
},
prevent_default: "onclick" prevent_default: "onclick"
} }
} }
@ -211,41 +218,41 @@ fn ListFooter(
let show_clear_completed = use_memo(move || todos.read().values().any(|todo| todo.checked)); let show_clear_completed = use_memo(move || todos.read().values().any(|todo| todo.checked));
rsx! { rsx! {
footer { class: "footer", footer { class: "footer",
span { class: "todo-count", span { class: "todo-count",
strong { "{active_todo_count} " } strong { "{active_todo_count} " }
span { span {
match active_todo_count() { match active_todo_count() {
1 => "item", 1 => "item",
_ => "items", _ => "items",
},
" left"
} }
" left"
} }
} ul { class: "filters",
ul { class: "filters", for (state , state_text , url) in [
for (state , state_text , url) in [ (FilterState::All, "All", "#/"),
(FilterState::All, "All", "#/"), (FilterState::Active, "Active", "#/active"),
(FilterState::Active, "Active", "#/active"), (FilterState::Completed, "Completed", "#/completed"),
(FilterState::Completed, "Completed", "#/completed"), ] {
] { li {
li { a {
a { href: url,
href: url, class: if filter() == state { "selected" },
class: if filter() == state { "selected" }, onclick: move |_| filter.set(state),
onclick: move |_| filter.set(state), prevent_default: "onclick",
prevent_default: "onclick", {state_text}
{state_text} }
} }
} }
} }
} if show_clear_completed() {
if show_clear_completed() { button {
button { class: "clear-completed",
class: "clear-completed", onclick: move |_| todos.write().retain(|_, todo| !todo.checked),
onclick: move |_| todos.write().retain(|_, todo| !todo.checked), "Clear completed"
"Clear completed" }
} }
} }
} }
}
} }

View file

@ -145,7 +145,7 @@ pub fn remove_future(id: Task) {
/// ///
/// pub fn use_custom_state() -> CustomState { /// pub fn use_custom_state() -> CustomState {
/// use_hook(|| CustomState { /// use_hook(|| CustomState {
/// inner: Signal::new(InnerCustomState) /// inner: Signal::new(InnerCustomState(0))
/// }) /// })
/// } /// }
/// ``` /// ```

View file

@ -310,7 +310,7 @@ impl Scope {
/// ///
/// pub fn use_custom_state() -> CustomState { /// pub fn use_custom_state() -> CustomState {
/// use_hook(|| CustomState { /// use_hook(|| CustomState {
/// inner: Signal::new(InnerCustomState) /// inner: Signal::new(InnerCustomState(0))
/// }) /// })
/// } /// }
/// ``` /// ```

View file

@ -138,7 +138,7 @@ use tracing::instrument;
/// ///
/// let dom = VirtualDom::new(app); /// let dom = VirtualDom::new(app);
/// ///
/// real_dom.apply(dom.rebuild()); /// dom.rebuild(real_dom.apply());
/// ///
/// loop { /// loop {
/// select! { /// select! {
@ -258,7 +258,7 @@ impl VirtualDom {
/// ///
/// ```rust, ignore /// ```rust, ignore
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" }); /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
/// let mutations = dom.rebuild(); /// dom.rebuild_in_place();
/// ``` /// ```
pub fn new_with_props<P: Clone + 'static, M: 'static>( pub fn new_with_props<P: Clone + 'static, M: 'static>(
root: impl ComponentFunction<P, M>, root: impl ComponentFunction<P, M>,
@ -302,7 +302,7 @@ impl VirtualDom {
/// ///
/// ```rust, ignore /// ```rust, ignore
/// let mut dom = VirtualDom::new_from_root(VComponent::new(Example, SomeProps { name: "jane" }, "Example")); /// let mut dom = VirtualDom::new_from_root(VComponent::new(Example, SomeProps { name: "jane" }, "Example"));
/// let mutations = dom.rebuild(); /// dom.rebuild(to_writer);
/// ``` /// ```
#[instrument(skip(root), level = "trace", name = "VirtualDom::new")] #[instrument(skip(root), level = "trace", name = "VirtualDom::new")]
pub(crate) fn new_with_component(root: impl AnyProps + 'static) -> Self { pub(crate) fn new_with_component(root: impl AnyProps + 'static) -> Self {
@ -611,9 +611,7 @@ impl VirtualDom {
/// static app: Component = |cx| rsx!{ "hello world" }; /// static app: Component = |cx| rsx!{ "hello world" };
/// ///
/// let mut dom = VirtualDom::new(); /// let mut dom = VirtualDom::new();
/// let edits = dom.rebuild(); /// dom.rebuild(to_writer);
///
/// apply_edits(edits);
/// ``` /// ```
#[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")] #[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")]
pub fn rebuild(&mut self, to: &mut impl WriteMutations) { pub fn rebuild(&mut self, to: &mut impl WriteMutations) {

View file

@ -1,4 +1,3 @@
use crate::dioxus_elements::SerializedMouseData;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_core::ElementId; use dioxus_core::ElementId;
use dioxus_elements::SerializedHtmlEventConverter; use dioxus_elements::SerializedHtmlEventConverter;

View file

@ -66,7 +66,7 @@ async fn spawn_forever_persists() {
#[component] #[component]
fn Child() -> Element { fn Child() -> Element {
spawn_forever(async move { spawn_forever(async move {
loop { for _ in 0..10 {
POLL_COUNT.fetch_add(1, Ordering::Relaxed); POLL_COUNT.fetch_add(1, Ordering::Relaxed);
tokio::time::sleep(Duration::from_millis(50)).await; tokio::time::sleep(Duration::from_millis(50)).await;
} }
@ -82,7 +82,9 @@ async fn spawn_forever_persists() {
tokio::select! { tokio::select! {
_ = dom.wait_for_work() => {} _ = dom.wait_for_work() => {}
_ = tokio::time::sleep(Duration::from_millis(500)) => {} // We intentionally wait a bit longer than 50ms*10 to make sure the test has time to finish
// Without the extra time, the test can fail on windows
_ = tokio::time::sleep(Duration::from_millis(1000)) => {}
}; };
// By the time the tasks are finished, we should've accumulated ticks from two tasks // By the time the tasks are finished, we should've accumulated ticks from two tasks

View file

@ -2,7 +2,6 @@ use std::collections::HashMap;
use dioxus::html::geometry::euclid::Vector3D; use dioxus::html::geometry::euclid::Vector3D;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_core::prelude::consume_context;
use dioxus_desktop::DesktopContext; use dioxus_desktop::DesktopContext;
#[path = "./utils.rs"] #[path = "./utils.rs"]
@ -179,7 +178,10 @@ fn test_mouse_dblclick_div() -> Element {
println!("{:?}", event.data); println!("{:?}", event.data);
assert!(event.data.modifiers().is_empty()); assert!(event.data.modifiers().is_empty());
assert!( assert!(
event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Primary), event
.data
.held_buttons()
.contains(dioxus_html::input_data::MouseButton::Primary),
); );
assert!( assert!(
event event
@ -282,7 +284,8 @@ fn test_mouse_scroll_div() -> Element {
onwheel: move |event| { onwheel: move |event| {
println!("{:?}", event.data); println!("{:?}", event.data);
let dioxus_html::geometry::WheelDelta::Pixels(delta) = event.data.delta() else { let dioxus_html::geometry::WheelDelta::Pixels(delta) = event.data.delta() else {
panic!("Expected delta to be in pixels") }; panic!("Expected delta to be in pixels")
};
assert_eq!(delta, Vector3D::new(1.0, 2.0, 3.0)); assert_eq!(delta, Vector3D::new(1.0, 2.0, 3.0));
RECEIVED_EVENTS.with_mut(|x| *x += 1); RECEIVED_EVENTS.with_mut(|x| *x += 1);
} }
@ -476,11 +479,16 @@ fn test_form_input() -> Element {
r#type: "text", r#type: "text",
name: "username", name: "username",
id: "form-username", id: "form-username",
oninput: set_username, oninput: set_username
} }
input { r#type: "text", name: "full-name", value: "lorem" } input { r#type: "text", name: "full-name", value: "lorem" }
input { r#type: "password", name: "password", value: "ipsum" } input { r#type: "password", name: "password", value: "ipsum" }
input { r#type: "radio", name: "color", value: "red", checked: true } input {
r#type: "radio",
name: "color",
value: "red",
checked: true
}
input { r#type: "radio", name: "color", value: "blue" } input { r#type: "radio", name: "color", value: "blue" }
button { r#type: "submit", value: "Submit", "Submit the form" } button { r#type: "submit", value: "Submit", "Submit the form" }
} }
@ -511,13 +519,21 @@ fn test_form_submit() -> Element {
rsx! { rsx! {
div { div {
h1 { "Form" } h1 { "Form" }
form { form { id: "form-submitter", onsubmit: set_values,
id: "form-submitter", input {
onsubmit: set_values, r#type: "text",
input { r#type: "text", name: "username", id: "username", value: "goodbye" } name: "username",
id: "username",
value: "goodbye"
}
input { r#type: "text", name: "full-name", value: "lorem" } input { r#type: "text", name: "full-name", value: "lorem" }
input { r#type: "password", name: "password", value: "ipsum" } input { r#type: "password", name: "password", value: "ipsum" }
input { r#type: "radio", name: "color", value: "red", checked: true } input {
r#type: "radio",
name: "color",
value: "red",
checked: true
}
input { r#type: "radio", name: "color", value: "blue" } input { r#type: "radio", name: "color", value: "blue" }
button { r#type: "submit", value: "Submit", "Submit the form" } button { r#type: "submit", value: "Submit", "Submit the form" }
} }
@ -547,9 +563,9 @@ fn test_select_multiple_options() -> Element {
assert_eq!(values, vec!["usa", "canada"]); assert_eq!(values, vec!["usa", "canada"]);
RECEIVED_EVENTS.with_mut(|x| *x += 1); RECEIVED_EVENTS.with_mut(|x| *x += 1);
}, },
option { id: "usa", value: "usa", "USA" } option { id: "usa", value: "usa", "USA" }
option { id: "canada", value: "canada", "Canada" } option { id: "canada", value: "canada", "Canada" }
option { id: "mexico", value: "mexico", selected: true, "Mexico" } option { id: "mexico", value: "mexico", selected: true, "Mexico" }
} }
} }
} }

View file

@ -1,5 +1,4 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_core::Element;
use dioxus_desktop::DesktopContext; use dioxus_desktop::DesktopContext;
#[path = "./utils.rs"] #[path = "./utils.rs"]
@ -53,26 +52,17 @@ fn check_html_renders() -> Element {
} }
let dyn_value = 0; let dyn_value = 0;
let dyn_element = rsx! { let dyn_element = rsx! { div { dangerous_inner_html: "<p>hello world</p>" } };
div {
dangerous_inner_html: "<p>hello world</p>",
}
};
rsx! { rsx! {
div { div { id: "main_div",
id: "main_div",
div { div {
width: "100px", width: "100px",
height: "100px", height: "100px",
color: "rgb({dyn_value}, {dyn_value}, {dyn_value})", color: "rgb({dyn_value}, {dyn_value}, {dyn_value})",
id: 5, id: 5,
input { input { "type": "checkbox" }
"type": "checkbox", h1 { "text" }
},
h1 {
"text"
}
{dyn_element} {dyn_element}
} }
} }

View file

@ -18,7 +18,7 @@ pub fn check_app_exits(app: fn() -> Element) {
}); });
LaunchBuilder::desktop() LaunchBuilder::desktop()
.with_cfg(Config::new().with_window(WindowBuilder::new().with_visible(true))) .with_cfg(Config::new().with_window(WindowBuilder::new().with_visible(false)))
.launch(app); .launch(app);
// Stop deadman's switch // Stop deadman's switch

View file

@ -19,6 +19,9 @@ dioxus-hooks = { workspace = true, optional = true }
dioxus-rsx = { workspace = true, optional = true } dioxus-rsx = { workspace = true, optional = true }
dioxus-signals = { workspace = true, optional = true } dioxus-signals = { workspace = true, optional = true }
[dev-dependencies]
dioxus = { workspace = true }
[features] [features]
default = ["macro", "html", "signals", "hooks"] default = ["macro", "html", "signals", "hooks"]
signals = ["dioxus-signals"] signals = ["dioxus-signals"]

View file

@ -1,5 +1,5 @@
<div> <div>
<h1>🌗🚀 Dioxus</h1> <h1>🌗🚀 Dioxus (lib)</h1>
<p> <p>
<strong>A concurrent, functional, virtual DOM for Rust</strong> <strong>A concurrent, functional, virtual DOM for Rust</strong>
</p> </p>
@ -38,9 +38,9 @@ Remember: Dioxus is a library for declaring interactive user interfaces—it is
All Dioxus apps are built by composing functions that return an `Element`. All Dioxus apps are built by composing functions that return an `Element`.
To launch an app, we use the `launch` method and use features in ``Cargo.toml`` to specify which renderer we want to use. In the launch function, we pass the app's root `Component`. To launch an app, we use the `launch` method and use features in `Cargo.toml` to specify which renderer we want to use. In the launch function, we pass the app's root `Component`.
```rust ```rust, no_run
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {

View file

@ -36,9 +36,9 @@ Remember: Dioxus is a library for declaring interactive user interfaces—it is
All Dioxus apps are built by composing functions that return an `Element`. All Dioxus apps are built by composing functions that return an `Element`.
To launch an app, we use the `launch` method and use features in ``Cargo.toml`` to specify which renderer we want to use. In the launch function, we pass the app's root `Component`. To launch an app, we use the `launch` method and use features in `Cargo.toml` to specify which renderer we want to use. In the launch function, we pass the app's root `Component`.
```rust ```rust, no_run
use dioxus::prelude::*; use dioxus::prelude::*;
fn main() { fn main() {

View file

@ -66,6 +66,9 @@ tokio = { workspace = true, features = ["rt", "sync"], optional = true }
dioxus-hot-reload = { workspace = true } dioxus-hot-reload = { workspace = true }
tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"], optional = true } tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"], optional = true }
[dev-dependencies]
dioxus = { workspace = true, features = ["fullstack"] }
[features] [features]
default = ["hot-reload"] default = ["hot-reload"]
hot-reload = ["serde_json"] hot-reload = ["serde_json"]

View file

@ -32,7 +32,7 @@ Fullstack utilities for the [`Dioxus`](https://dioxuslabs.com) framework.
Full stack Dioxus in under 30 lines of code Full stack Dioxus in under 30 lines of code
```rust ```rust, no_run
#![allow(non_snake_case)] #![allow(non_snake_case)]
use dioxus::prelude::*; use dioxus::prelude::*;
@ -42,7 +42,7 @@ fn main() {
#[component] #[component]
fn App() -> Element { fn App() -> Element {
let meaning = use_signal(|| None); let mut meaning = use_signal(|| None);
rsx! { rsx! {
h1 { "Meaning of life: {meaning:?}" } h1 { "Meaning of life: {meaning:?}" }

View file

@ -12,9 +12,9 @@ use serde::{de::DeserializeOwned, Serialize};
/// use dioxus_fullstack::prelude::*; /// use dioxus_fullstack::prelude::*;
/// ///
/// fn app() -> Element { /// fn app() -> Element {
/// let state1 = server_cached(|| from_server(|| { /// let state1 = server_cached(|| {
/// 1234 /// 1234
/// })); /// });
/// ///
/// None /// None
/// } /// }

View file

@ -34,7 +34,7 @@ use crate::use_callback;
/// #[component] /// #[component]
/// fn Comp(count: u32) -> Element { /// fn Comp(count: u32) -> Element {
/// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes. /// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
/// use_effect(use_reactive((&count, |(count,)| println!("Manually manipulate the dom") ))); /// use_effect(use_reactive((&count,), |(count,)| println!("Manually manipulate the dom") ));
/// ///
/// todo!() /// todo!()
/// } /// }

View file

@ -32,7 +32,7 @@ use dioxus_signals::{Memo, Signal};
/// #[component] /// #[component]
/// fn Comp(count: u32) -> Element { /// fn Comp(count: u32) -> Element {
/// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes. /// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
/// let new_count = use_memo(use_reactive((&count, |(count,)| count + 1))); /// let new_count = use_memo(use_reactive((&count,), |(count,)| count + 1));
/// ///
/// todo!() /// todo!()
/// } /// }

View file

@ -74,7 +74,7 @@ impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G =
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust, no_run
/// use dioxus::prelude::*; /// use dioxus::prelude::*;
/// ///
/// let data = 5; /// let data = 5;
@ -104,7 +104,7 @@ pub fn use_reactive<O, D: Dependency>(
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust, no_run
/// use dioxus::prelude::*; /// use dioxus::prelude::*;
/// ///
/// let data = 5; /// let data = 5;

View file

@ -2,10 +2,6 @@
use crate::{use_callback, use_signal, UseCallback}; use crate::{use_callback, use_signal, UseCallback};
use dioxus_core::prelude::*; use dioxus_core::prelude::*;
use dioxus_core::{
prelude::{spawn, use_hook},
Task,
};
use dioxus_signals::*; use dioxus_signals::*;
use futures_util::{future, pin_mut, FutureExt, StreamExt}; use futures_util::{future, pin_mut, FutureExt, StreamExt};
use std::ops::Deref; use std::ops::Deref;
@ -66,7 +62,7 @@ use std::{cell::Cell, future::Future, rc::Rc};
/// #[component] /// #[component]
/// fn Comp(count: u32) -> Element { /// fn Comp(count: u32) -> Element {
/// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes. /// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
/// let new_count = use_resource(use_reactive((&count, |(count,)| async move {count + 1} ))); /// let new_count = use_resource(use_reactive((&count,), |(count,)| async move {count + 1} ));
/// ///
/// todo!() /// todo!()
/// } /// }

View file

@ -25,7 +25,7 @@ The Dioxus `rsx!` and `html!` macros can accept any compile-time correct namespa
However, this abstraction enables you to add any namespace of elements, provided they're in scope when rsx! is called. For an example, a UI that is designed for Augmented Reality might use different primitives than HTML: However, this abstraction enables you to add any namespace of elements, provided they're in scope when rsx! is called. For an example, a UI that is designed for Augmented Reality might use different primitives than HTML:
```rust ```rust, ignore
use ar_namespace::*; use ar_namespace::*;
rsx! { rsx! {
@ -46,7 +46,7 @@ This is currently a not-very-explored part of Dioxus. However, the namespacing s
Elements for dioxus must implement the (simple) DioxusElement trait to be used in the rsx! macro. Elements for dioxus must implement the (simple) DioxusElement trait to be used in the rsx! macro.
```rust ```rust, ignore
struct div; struct div;
impl DioxusElement for div { impl DioxusElement for div {
const TAG_NAME: &'static str = "div"; const TAG_NAME: &'static str = "div";
@ -60,7 +60,7 @@ Attributes would then be implemented as constants on these unit structs.
The HTML namespace is defined mostly with macros. However, the expanded form would look something like this: The HTML namespace is defined mostly with macros. However, the expanded form would look something like this:
```rust ```rust, ignore
struct base; struct base;
impl DioxusElement for base { impl DioxusElement for base {
const TAG_NAME: &'static str = "base"; const TAG_NAME: &'static str = "base";
@ -78,7 +78,7 @@ Because attributes are defined as methods on the unit struct, they guard the att
Whenever the rsx! macro is called, it relies on a module `dioxus_elements` to be in scope. When you enable the `html` feature in dioxus, this module gets imported in the prelude. However, you can extend this with your own set of custom elements by making your own `dioxus_elements` module and re-exporting the html namespace. Whenever the rsx! macro is called, it relies on a module `dioxus_elements` to be in scope. When you enable the `html` feature in dioxus, this module gets imported in the prelude. However, you can extend this with your own set of custom elements by making your own `dioxus_elements` module and re-exporting the html namespace.
```rust ```rust, ignore
mod dioxus_elements { mod dioxus_elements {
use dioxus::prelude::dioxus_elements::*; use dioxus::prelude::dioxus_elements::*;
struct my_element; struct my_element;

View file

@ -106,7 +106,9 @@ fn handle_edits_code() -> String {
/// If you enter a relative path, the web client automatically prefixes the host address in /// If you enter a relative path, the web client automatically prefixes the host address in
/// `window.location` when creating a web socket to LiveView. /// `window.location` when creating a web socket to LiveView.
/// ///
/// ``` /// ```rust
/// use dioxus_liveview::interpreter_glue;
///
/// // Creates websocket connection to same host as current page /// // Creates websocket connection to same host as current page
/// interpreter_glue("/api/liveview"); /// interpreter_glue("/api/liveview");
/// ///

View file

@ -20,6 +20,9 @@ quote = { workspace = true }
proc-macro2 = { workspace = true } proc-macro2 = { workspace = true }
slab = { workspace = true } slab = { workspace = true }
[dev-dependencies]
dioxus = { workspace = true, features = ["router"] }
[features] [features]
default = [] default = []
web = [] web = []

View file

@ -42,7 +42,9 @@ mod segment;
/// 2. By the order they are defined in the enum /// 2. By the order they are defined in the enum
/// ///
/// All features: /// All features:
/// ```rust, skip /// ```rust
/// use dioxus::prelude::*;
///
/// #[rustfmt::skip] /// #[rustfmt::skip]
/// #[derive(Clone, Debug, PartialEq, Routable)] /// #[derive(Clone, Debug, PartialEq, Routable)]
/// enum Route { /// enum Route {
@ -75,9 +77,17 @@ mod segment;
/// #[redirect("/:id/user", |id: usize| Route::Route3 { dynamic: id.to_string()})] /// #[redirect("/:id/user", |id: usize| Route::Route3 { dynamic: id.to_string()})]
/// #[route("/:dynamic")] /// #[route("/:dynamic")]
/// Route3 { dynamic: String }, /// Route3 { dynamic: String },
/// #[child]
/// NestedRoute(NestedRoute),
/// } /// }
/// # #[component]
/// # fn Route1(user_id: usize, dynamic: usize, query: String) -> Element { None }
/// # #[component]
/// # fn Route2(user_id: usize) -> Element { None }
/// # #[component]
/// # fn Route3(dynamic: String) -> Element { None }
/// # #[component]
/// # fn UserFrame(user_id: usize) -> Element { None }
/// # #[component]
/// # fn IndexComponent() -> Element { None }
/// ``` /// ```
/// ///
/// # `#[route("path", component)]` /// # `#[route("path", component)]`
@ -89,7 +99,9 @@ mod segment;
/// Routes are the most basic attribute. They allow you to define a route and the component to render when the route is matched. The component must take all dynamic parameters of the route and all parent nests. /// Routes are the most basic attribute. They allow you to define a route and the component to render when the route is matched. The component must take all dynamic parameters of the route and all parent nests.
/// The next variant will be tied to the component. If you link to that variant, the component will be rendered. /// The next variant will be tied to the component. If you link to that variant, the component will be rendered.
/// ///
/// ```rust, skip /// ```rust
/// use dioxus::prelude::*;
///
/// #[derive(Clone, Debug, PartialEq, Routable)] /// #[derive(Clone, Debug, PartialEq, Routable)]
/// enum Route { /// enum Route {
/// // Define routes that renders the IndexComponent /// // Define routes that renders the IndexComponent
@ -97,6 +109,8 @@ mod segment;
/// #[route("/", Index)] /// #[route("/", Index)]
/// Index {}, /// Index {},
/// } /// }
/// # #[component]
/// # fn Index() -> Element { None }
/// ``` /// ```
/// ///
/// # `#[redirect("path", function)]` /// # `#[redirect("path", function)]`
@ -105,14 +119,18 @@ mod segment;
/// - `path`: The path to the enum variant (relative to the parent nest) /// - `path`: The path to the enum variant (relative to the parent nest)
/// - `function`: A function that takes the parameters from the path and returns a new route /// - `function`: A function that takes the parameters from the path and returns a new route
/// ///
/// ```rust, skip /// ```rust
/// use dioxus::prelude::*;
///
/// #[derive(Clone, Debug, PartialEq, Routable)] /// #[derive(Clone, Debug, PartialEq, Routable)]
/// enum Route { /// enum Route {
/// // Redirects the /:id route to the Index route /// // Redirects the /:id route to the Index route
/// #[redirect("/:id", |_: usize| Route::Index {})] /// #[redirect("/:id", |id: usize| Route::Index {})]
/// #[route("/", Index)] /// #[route("/", Index)]
/// Index {}, /// Index {},
/// } /// }
/// # #[component]
/// # fn Index() -> Element { None }
/// ``` /// ```
/// ///
/// Redirects allow you to redirect a route to another route. The function must take all dynamic parameters of the route and all parent nests. /// Redirects allow you to redirect a route to another route. The function must take all dynamic parameters of the route and all parent nests.
@ -124,29 +142,35 @@ mod segment;
/// ///
/// Nests effect all nests, routes and redirects defined until the next `#[end_nest]` attribute. All children of nests are relative to the nest route and must include all dynamic parameters of the nest. /// Nests effect all nests, routes and redirects defined until the next `#[end_nest]` attribute. All children of nests are relative to the nest route and must include all dynamic parameters of the nest.
/// ///
/// ```rust, skip /// ```rust
/// use dioxus::prelude::*;
///
/// #[derive(Clone, Debug, PartialEq, Routable)] /// #[derive(Clone, Debug, PartialEq, Routable)]
/// enum Route { /// enum Route {
/// // Nests all child routes in the /blog route /// // Nests all child routes in the /blog route
/// #[nest("/blog")] /// #[nest("/blog")]
/// // This is at /blog/:id /// // This is at /blog/:id
/// #[redirect("/:id", |_: usize| Route::Index {})] /// #[redirect("/:id", |id: usize| Route::Index {})]
/// // This is at /blog /// // This is at /blog
/// #[route("/", Index)] /// #[route("/", Index)]
/// Index {}, /// Index {},
/// } /// }
/// # #[component]
/// # fn Index() -> Element { None }
/// ``` /// ```
/// ///
/// # `#[end_nest]` /// # `#[end_nest]`
/// ///
/// The `#[end_nest]` attribute is used to end a nest. It takes no parameters. /// The `#[end_nest]` attribute is used to end a nest. It takes no parameters.
/// ///
/// ```rust, skip /// ```rust
/// use dioxus::prelude::*;
///
/// #[derive(Clone, Debug, PartialEq, Routable)] /// #[derive(Clone, Debug, PartialEq, Routable)]
/// enum Route { /// enum Route {
/// #[nest("/blog")] /// #[nest("/blog")]
/// // This is at /blog/:id /// // This is at /blog/:id
/// #[redirect("/:id", |_: usize| Route::Index {})] /// #[redirect("/:id", |id: usize| Route::Index {})]
/// // This is at /blog /// // This is at /blog
/// #[route("/", Index)] /// #[route("/", Index)]
/// Index {}, /// Index {},
@ -156,6 +180,10 @@ mod segment;
/// #[route("/")] /// #[route("/")]
/// Home {}, /// Home {},
/// } /// }
/// # #[component]
/// # fn Index() -> Element { None }
/// # #[component]
/// # fn Home() -> Element { None }
/// ``` /// ```
/// ///
/// # `#[layout(component)]` /// # `#[layout(component)]`
@ -165,26 +193,34 @@ mod segment;
/// ///
/// The layout component allows you to wrap all children of the layout in a component. The child routes are rendered in the Outlet of the layout component. The layout component must take all dynamic parameters of the nests it is nested in. /// The layout component allows you to wrap all children of the layout in a component. The child routes are rendered in the Outlet of the layout component. The layout component must take all dynamic parameters of the nests it is nested in.
/// ///
/// ```rust, skip /// ```rust
/// use dioxus::prelude::*;
///
/// #[derive(Clone, Debug, PartialEq, Routable)] /// #[derive(Clone, Debug, PartialEq, Routable)]
/// enum Route { /// enum Route {
/// #[layout(BlogFrame)] /// #[layout(BlogFrame)]
/// #[redirect("/:id", |_: usize| Route::Index {})] /// #[redirect("/:id", |id: usize| Route::Index {})]
/// // Index will be rendered in the Outlet of the BlogFrame component /// // Index will be rendered in the Outlet of the BlogFrame component
/// #[route("/", Index)] /// #[route("/", Index)]
/// Index {}, /// Index {},
/// } /// }
/// # #[component]
/// # fn Index() -> Element { None }
/// # #[component]
/// # fn BlogFrame() -> Element { None }
/// ``` /// ```
/// ///
/// # `#[end_layout]` /// # `#[end_layout]`
/// ///
/// The `#[end_layout]` attribute is used to end a layout. It takes no parameters. /// The `#[end_layout]` attribute is used to end a layout. It takes no parameters.
/// ///
/// ```rust, skip /// ```rust
/// use dioxus::prelude::*;
///
/// #[derive(Clone, Debug, PartialEq, Routable)] /// #[derive(Clone, Debug, PartialEq, Routable)]
/// enum Route { /// enum Route {
/// #[layout(BlogFrame)] /// #[layout(BlogFrame)]
/// #[redirect("/:id", |_: usize| Route::Index {})] /// #[redirect("/:id", |id: usize| Route::Index {})]
/// // Index will be rendered in the Outlet of the BlogFrame component /// // Index will be rendered in the Outlet of the BlogFrame component
/// #[route("/", Index)] /// #[route("/", Index)]
/// Index {}, /// Index {},
@ -194,6 +230,12 @@ mod segment;
/// #[route("/")] /// #[route("/")]
/// Home {}, /// Home {},
/// } /// }
/// # #[component]
/// # fn Index() -> Element { None }
/// # #[component]
/// # fn BlogFrame() -> Element { None }
/// # #[component]
/// # fn Home() -> Element { None }
/// ``` /// ```
#[proc_macro_derive( #[proc_macro_derive(
Routable, Routable,

View file

@ -73,7 +73,12 @@ pub fn GoBackButton(props: HistoryButtonProps) -> Element {
let disabled = !router.can_go_back(); let disabled = !router.can_go_back();
rsx! { rsx! {
button { disabled: "{disabled}", prevent_default: "onclick", onclick: move |_| router.go_back(), {children} } button {
disabled: "{disabled}",
prevent_default: "onclick",
onclick: move |_| router.go_back(),
{children}
}
} }
} }
@ -114,7 +119,7 @@ pub fn GoBackButton(props: HistoryButtonProps) -> Element {
/// } /// }
/// # /// #
/// # let mut vdom = VirtualDom::new(App); /// # let mut vdom = VirtualDom::new(App);
/// # let _ = vdom.rebuild(); /// # vdom.rebuild_in_place();
/// # assert_eq!( /// # assert_eq!(
/// # dioxus_ssr::render(&vdom), /// # dioxus_ssr::render(&vdom),
/// # r#"<button disabled="true" dioxus-prevent-default="onclick">go forward</button>"# /// # r#"<button disabled="true" dioxus-prevent-default="onclick">go forward</button>"#
@ -139,6 +144,11 @@ pub fn GoForwardButton(props: HistoryButtonProps) -> Element {
let disabled = !router.can_go_forward(); let disabled = !router.can_go_forward();
rsx! { rsx! {
button { disabled: "{disabled}", prevent_default: "onclick", onclick: move |_| router.go_forward(), {children} } button {
disabled: "{disabled}",
prevent_default: "onclick",
onclick: move |_| router.go_forward(),
{children}
}
} }
} }

View file

@ -178,26 +178,24 @@ impl Debug for LinkProps {
/// #[component] /// #[component]
/// fn Index() -> Element { /// fn Index() -> Element {
/// rsx! { /// rsx! {
/// rsx! { /// Link {
/// Link { /// active_class: "active",
/// active_class: "active", /// class: "link_class",
/// class: "link_class", /// id: "link_id",
/// id: "link_id", /// new_tab: true,
/// new_tab: true, /// rel: "link_rel",
/// rel: "link_rel", /// to: Route::Index {},
/// to: Route::Index {},
/// ///
/// "A fully configured link" /// "A fully configured link"
/// }
/// } /// }
/// } /// }
/// } /// }
/// # /// #
/// # let mut vdom = VirtualDom::new(App); /// # let mut vdom = VirtualDom::new(App);
/// # let _ = vdom.rebuild(); /// # vdom.rebuild_in_place();
/// # assert_eq!( /// # assert_eq!(
/// # dioxus_ssr::render(&vdom), /// # dioxus_ssr::render(&vdom),
/// # r#"<a href="/" dioxus-prevent-default="" class="link_class active" id="link_id" rel="link_rel" target="_blank">A fully configured link</a>"# /// # r#"<a href="/" dioxus-prevent-default="" class="link_class active" rel="link_rel" target="_blank" id="link_id">A fully configured link</a>"#
/// # ); /// # );
/// ``` /// ```
#[allow(non_snake_case)] #[allow(non_snake_case)]

View file

@ -65,7 +65,7 @@ use dioxus_lib::prelude::*;
/// # } /// # }
/// # /// #
/// # let mut vdom = VirtualDom::new(App); /// # let mut vdom = VirtualDom::new(App);
/// # let _ = vdom.rebuild(); /// # vdom.rebuild_in_place();
/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><p>Child</p>"); /// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><p>Child</p>");
/// ``` /// ```
pub fn Outlet<R: Routable + Clone>() -> Element { pub fn Outlet<R: Routable + Clone>() -> Element {

View file

@ -46,7 +46,7 @@ use crate::prelude::{Navigator, RouterContext};
/// } /// }
/// ///
/// # let mut vdom = VirtualDom::new(App); /// # let mut vdom = VirtualDom::new(App);
/// # let _ = vdom.rebuild(); /// # vdom.rebuild_in_place();
/// ``` /// ```
#[must_use] #[must_use]
pub fn use_navigator() -> Navigator { pub fn use_navigator() -> Navigator {

View file

@ -37,7 +37,7 @@ use crate::utils::use_router_internal::use_router_internal;
/// } /// }
/// # /// #
/// # let mut vdom = VirtualDom::new(App); /// # let mut vdom = VirtualDom::new(App);
/// # let _ = vdom.rebuild(); /// # vdom.rebuild_in_place();
/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><h2>Current Path</h2><p>/</p>") /// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><h2>Current Path</h2><p>/</p>")
/// ``` /// ```
#[must_use] #[must_use]

View file

@ -21,7 +21,7 @@ Dioxus SSR provides utilities to render Dioxus components to valid HTML. Once re
let app: Component = |cx| rsx!(div {"hello world!"}); let app: Component = |cx| rsx!(div {"hello world!"});
let mut vdom = VirtualDom::new(app); let mut vdom = VirtualDom::new(app);
let _ = vdom.rebuild(); vdom.rebuild_in_place();
let text = dioxus_ssr::render(&vdom); let text = dioxus_ssr::render(&vdom);
assert_eq!(text, "<div>hello world!</div>") assert_eq!(text, "<div>hello world!</div>")
@ -45,7 +45,7 @@ let content = dioxus_ssr::render_element(rsx!{
```rust, ignore ```rust, ignore
let mut vdom = VirtualDom::new(app); let mut vdom = VirtualDom::new(app);
let _ = vdom.rebuild(); vdom.rebuild_in_place();
let content = dioxus_ssr::render(&vdom); let content = dioxus_ssr::render(&vdom);
``` ```
@ -63,7 +63,7 @@ To enable pre-rendering, simply set the pre-rendering flag to true.
```rust, ignore ```rust, ignore
let mut vdom = VirtualDom::new(App); let mut vdom = VirtualDom::new(App);
let _ = vdom.rebuild(); vdom.rebuild_in_place();
let mut renderer = dioxus_ssr::Renderer::new(); let mut renderer = dioxus_ssr::Renderer::new();
renderer.pre_render = true; renderer.pre_render = true;