From d10d546418aecd4b2116e1099345dbed59290fbb Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 29 Mar 2026 11:54:20 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20(pagetop):=20Mejora=20API=20y=20doc?= =?UTF-8?q?.=20de=20Children?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `From for ChildOp: with_child()` acepta componentes directamente sin envolverlos en `Child::with(...)`. - `From for ChildOp` para completar las conversiones implícitas. - Actualiza ejemplos y tests con la nueva API en bootsier y aliner. --- examples/hello-name.rs | 2 +- examples/hello-world.rs | 2 +- examples/navbar-menus.rs | 50 +++---- extensions/pagetop-aliner/src/lib.rs | 6 +- .../src/theme/container/component.rs | 14 +- .../pagetop-bootsier/src/theme/dropdown.rs | 10 +- .../src/theme/dropdown/component.rs | 25 ++-- .../src/theme/form/component.rs | 16 +-- .../src/theme/form/fieldset.rs | 14 +- extensions/pagetop-bootsier/src/theme/nav.rs | 14 +- .../src/theme/nav/component.rs | 25 ++-- .../pagetop-bootsier/src/theme/navbar.rs | 54 ++++---- .../src/theme/navbar/component.rs | 26 ++-- .../pagetop-bootsier/src/theme/offcanvas.rs | 8 +- .../src/theme/offcanvas/component.rs | 14 +- src/base/component/block.rs | 14 +- src/base/component/intro.rs | 20 +-- src/base/extension/welcome.rs | 10 +- src/base/theme/basic.rs | 2 +- src/core/component.rs | 3 +- src/core/component/children.rs | 91 ++++++++++--- src/core/component/context.rs | 23 +++- src/core/theme/definition.rs | 45 ++++--- src/core/theme/regions.rs | 13 +- src/lib.rs | 2 +- src/response/page.rs | 30 ++--- tests/component_children.rs | 126 +++++++++++------- 27 files changed, 346 insertions(+), 313 deletions(-) diff --git a/examples/hello-name.rs b/examples/hello-name.rs index b6f9c113..e2904c6f 100644 --- a/examples/hello-name.rs +++ b/examples/hello-name.rs @@ -14,7 +14,7 @@ async fn hello_name( ) -> ResultPage { let name = path.into_inner(); Page::new(request) - .add_child(Html::with(move |_| { + .with_child(Html::with(move |_| { html! { h1 style="text-align: center;" { "Hello " (name) "!" } } diff --git a/examples/hello-world.rs b/examples/hello-world.rs index 74727ac2..e6127af9 100644 --- a/examples/hello-world.rs +++ b/examples/hello-world.rs @@ -10,7 +10,7 @@ impl Extension for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage { Page::new(request) - .add_child(Html::with(|_| { + .with_child(Html::with(|_| { html! { h1 style="text-align: center;" { "Hello World!" } } diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs index 11d6efc1..15065131 100644 --- a/examples/navbar-menus.rs +++ b/examples/navbar-menus.rs @@ -12,79 +12,79 @@ impl Extension for SuperMenu { fn initialize(&self) { let navbar_menu = Navbar::brand_left(navbar::Brand::new()) .with_expand(BreakPoint::LG) - .add_item(navbar::Item::nav( + .with_item(navbar::Item::nav( Nav::new() - .add_item(nav::Item::link(L10n::l("sample_menus_item_link"), |cx| { + .with_item(nav::Item::link(L10n::l("sample_menus_item_link"), |cx| { cx.route("/") })) - .add_item(nav::Item::link_blank( + .with_item(nav::Item::link_blank( L10n::l("sample_menus_item_blank"), |_| "https://docs.rs/pagetop".into(), )) - .add_item(nav::Item::dropdown( + .with_item(nav::Item::dropdown( Dropdown::new() .with_title(L10n::l("sample_menus_test_title")) - .add_item(dropdown::Item::header(L10n::l("sample_menus_dev_header"))) - .add_item(dropdown::Item::link( + .with_item(dropdown::Item::header(L10n::l("sample_menus_dev_header"))) + .with_item(dropdown::Item::link( L10n::l("sample_menus_dev_getting_started"), |cx| cx.route("/dev/getting-started"), )) - .add_item(dropdown::Item::link( + .with_item(dropdown::Item::link( L10n::l("sample_menus_dev_guides"), |cx| cx.route("/dev/guides"), )) - .add_item(dropdown::Item::link_blank( + .with_item(dropdown::Item::link_blank( L10n::l("sample_menus_dev_forum"), |_| "https://forum.example.dev".into(), )) - .add_item(dropdown::Item::divider()) - .add_item(dropdown::Item::header(L10n::l("sample_menus_sdk_header"))) - .add_item(dropdown::Item::link( + .with_item(dropdown::Item::divider()) + .with_item(dropdown::Item::header(L10n::l("sample_menus_sdk_header"))) + .with_item(dropdown::Item::link( L10n::l("sample_menus_sdk_rust"), |cx| cx.route("/dev/sdks/rust"), )) - .add_item(dropdown::Item::link(L10n::l("sample_menus_sdk_js"), |cx| { + .with_item(dropdown::Item::link(L10n::l("sample_menus_sdk_js"), |cx| { cx.route("/dev/sdks/js") })) - .add_item(dropdown::Item::link( + .with_item(dropdown::Item::link( L10n::l("sample_menus_sdk_python"), |cx| cx.route("/dev/sdks/python"), )) - .add_item(dropdown::Item::divider()) - .add_item(dropdown::Item::header(L10n::l( + .with_item(dropdown::Item::divider()) + .with_item(dropdown::Item::header(L10n::l( "sample_menus_plugin_header", ))) - .add_item(dropdown::Item::link( + .with_item(dropdown::Item::link( L10n::l("sample_menus_plugin_auth"), |cx| cx.route("/dev/sdks/rust/plugins/auth"), )) - .add_item(dropdown::Item::link( + .with_item(dropdown::Item::link( L10n::l("sample_menus_plugin_cache"), |cx| cx.route("/dev/sdks/rust/plugins/cache"), )) - .add_item(dropdown::Item::divider()) - .add_item(dropdown::Item::label(L10n::l("sample_menus_item_label"))) - .add_item(dropdown::Item::link_disabled( + .with_item(dropdown::Item::divider()) + .with_item(dropdown::Item::label(L10n::l("sample_menus_item_label"))) + .with_item(dropdown::Item::link_disabled( L10n::l("sample_menus_item_disabled"), |cx| cx.route("#"), )), )) - .add_item(nav::Item::link_disabled( + .with_item(nav::Item::link_disabled( L10n::l("sample_menus_item_disabled"), |cx| cx.route("#"), )), )) - .add_item(navbar::Item::nav( + .with_item(navbar::Item::nav( Nav::new() .with_classes( ClassesOp::Add, classes::Margin::with(Side::Start, ScaleSize::Auto).to_class(), ) - .add_item(nav::Item::link( + .with_item(nav::Item::link( L10n::l("sample_menus_item_sign_up"), |cx| cx.route("/auth/sign-up"), )) - .add_item(nav::Item::link(L10n::l("sample_menus_item_login"), |cx| { + .with_item(nav::Item::link(L10n::l("sample_menus_item_login"), |cx| { cx.route("/auth/login") })), )); @@ -92,7 +92,7 @@ impl Extension for SuperMenu { InRegion::Global(&DefaultRegion::Header).add( Container::new() .with_width(container::Width::FluidMax(UnitValue::RelRem(75.0))) - .add_child(navbar_menu), + .with_child(navbar_menu), ); } } diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index aa0e6723..e0421305 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -69,10 +69,10 @@ use pagetop_aliner::Aliner; async fn homepage(request: HttpRequest) -> ResultPage { Page::new(request) .with_theme(&Aliner) - .add_child( + .with_child( Block::new() .with_title(L10n::l("sample_title")) - .add_child(Html::with(|cx| html! { + .with_child(Html::with(|cx| html! { p { (L10n::l("sample_content").using(cx)) } })), ) @@ -122,7 +122,7 @@ impl Theme for Aliner { )) .alter_child_in( &DefaultRegion::Footer, - ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), + ChildOp::AddIfEmpty(PoweredBy::new().into()), ); } } diff --git a/extensions/pagetop-bootsier/src/theme/container/component.rs b/extensions/pagetop-bootsier/src/theme/container/component.rs index 8deb7552..0635ad18 100644 --- a/extensions/pagetop-bootsier/src/theme/container/component.rs +++ b/extensions/pagetop-bootsier/src/theme/container/component.rs @@ -150,17 +150,11 @@ impl Container { self } - /// Añade un nuevo componente hijo al contenedor. - #[inline] - pub fn add_child(mut self, component: impl Component) -> Self { - self.children.add(Child::with(component)); - self - } - - /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. + /// Añade un nuevo componente al contenedor o modifica la lista de componentes (`children`) con + /// una operación [`ChildOp`]. #[builder_fn] - pub fn with_child(mut self, op: ChildOp) -> Self { - self.children.alter_child(op); + pub fn with_child(mut self, op: impl Into) -> Self { + self.children.alter_child(op.into()); self } } diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs index ec62c531..213756c7 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown.rs @@ -17,11 +17,11 @@ //! .with_button_color(ButtonColor::Background(Color::Secondary)) //! .with_auto_close(dropdown::AutoClose::ClickableInside) //! .with_direction(dropdown::Direction::Dropend) -//! .add_item(dropdown::Item::link(L10n::n("Home"), |_| "/".into())) -//! .add_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://google.es".into())) -//! .add_item(dropdown::Item::divider()) -//! .add_item(dropdown::Item::header(L10n::n("User session"))) -//! .add_item(dropdown::Item::button(L10n::n("Sign out"))); +//! .with_item(dropdown::Item::link(L10n::n("Home"), |_| "/".into())) +//! .with_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://docs.rs".into())) +//! .with_item(dropdown::Item::divider()) +//! .with_item(dropdown::Item::header(L10n::n("User session"))) +//! .with_item(dropdown::Item::button(L10n::n("Sign out"))); //! ``` mod props; diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index 988e94a3..833cf40b 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -240,27 +240,22 @@ impl Dropdown { self } - /// Añade un nuevo elemento hijo al menú. - #[inline] - pub fn add_item(mut self, item: dropdown::Item) -> Self { - self.items.add(Child::with(item)); - self - } - - /// Modifica la lista de elementos del menú aplicando una operación [`ChildOp`]. + /// Añade un nuevo elemento al menú o modifica la lista de elementos del menú con una operación + /// [`ChildOp`]. /// - /// Para añadir elementos usa [`Child::with(item)`](Child::with): + /// # Ejemplo /// /// ```rust,ignore - /// dropdown.with_items(ChildOp::Add(Child::with(dropdown::Item::link(...)))); - /// dropdown.with_items(ChildOp::AddMany(vec![ - /// Child::with(dropdown::Item::link(...)), - /// Child::with(dropdown::Item::divider()), + /// dropdown.with_item(dropdown::Item::link("Opción", "/ruta")); + /// dropdown.with_item(ChildOp::AddMany(vec![ + /// dropdown::Item::link(...).into(), + /// dropdown::Item::divider().into(), + /// dropdown::Item::link(...).into(), /// ])); /// ``` #[builder_fn] - pub fn with_items(mut self, op: ChildOp) -> Self { - self.items.alter_child(op); + pub fn with_item(mut self, op: impl Into) -> Self { + self.items.alter_child(op.into()); self } } diff --git a/extensions/pagetop-bootsier/src/theme/form/component.rs b/extensions/pagetop-bootsier/src/theme/form/component.rs index 0dacfdea..10dd803c 100644 --- a/extensions/pagetop-bootsier/src/theme/form/component.rs +++ b/extensions/pagetop-bootsier/src/theme/form/component.rs @@ -25,7 +25,7 @@ use crate::theme::form; /// .with_action("/search") /// .with_method(form::Method::Get) /// .with_classes(ClassesOp::Add, "mb-3") -/// .add_child(Input::new().with_name("q")); +/// .with_child(Input::new().with_name("q")); /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Form { @@ -114,17 +114,11 @@ impl Form { self } - /// Añade un nuevo componente hijo al formulario. - #[inline] - pub fn add_child(mut self, component: impl Component) -> Self { - self.children.add(Child::with(component)); - self - } - - /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. + /// Añade un nuevo componente al formulario o modifica la lista de de componentes (`children`) + /// con una operación [`ChildOp`]. #[builder_fn] - pub fn with_child(mut self, op: ChildOp) -> Self { - self.children.alter_child(op); + pub fn with_child(mut self, op: impl Into) -> Self { + self.children.alter_child(op.into()); self } } diff --git a/extensions/pagetop-bootsier/src/theme/form/fieldset.rs b/extensions/pagetop-bootsier/src/theme/form/fieldset.rs index 287e2655..02567373 100644 --- a/extensions/pagetop-bootsier/src/theme/form/fieldset.rs +++ b/extensions/pagetop-bootsier/src/theme/form/fieldset.rs @@ -65,17 +65,11 @@ impl Fieldset { self } - /// Añade un nuevo componente hijo al `fieldset`. - #[inline] - pub fn add_child(mut self, component: impl Component) -> Self { - self.children.add(Child::with(component)); - self - } - - /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. + /// Añade un nuevo componente al `fieldset` o modifica la lista de de componentes (`children`) + /// con una operación [`ChildOp`]. #[builder_fn] - pub fn with_child(mut self, op: ChildOp) -> Self { - self.children.alter_child(op); + pub fn with_child(mut self, op: impl Into) -> Self { + self.children.alter_child(op.into()); self } } diff --git a/extensions/pagetop-bootsier/src/theme/nav.rs b/extensions/pagetop-bootsier/src/theme/nav.rs index e6e2f813..16b3d2d7 100644 --- a/extensions/pagetop-bootsier/src/theme/nav.rs +++ b/extensions/pagetop-bootsier/src/theme/nav.rs @@ -14,17 +14,17 @@ //! # use pagetop_bootsier::prelude::*; //! let nav = Nav::tabs() //! .with_layout(nav::Layout::End) -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .add_item(nav::Item::link_blank(L10n::n("External"), |_| "https://google.es".into())) -//! .add_item(nav::Item::dropdown( +//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .with_item(nav::Item::link_blank(L10n::n("External"), |_| "https://docs.rs".into())) +//! .with_item(nav::Item::dropdown( //! Dropdown::new() //! .with_title(L10n::n("Options")) -//! .with_items(ChildOp::AddMany(vec![ -//! Child::with(dropdown::Item::link(L10n::n("Action"), |_| "/action".into())), -//! Child::with(dropdown::Item::link(L10n::n("Another"), |_| "/another".into())), +//! .with_item(ChildOp::AddMany(vec![ +//! dropdown::Item::link(L10n::n("Action"), |_| "/action".into()).into(), +//! dropdown::Item::link(L10n::n("Another"), |_| "/another".into()).into(), //! ])), //! )) -//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())); +//! .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())); //! ``` mod props; diff --git a/extensions/pagetop-bootsier/src/theme/nav/component.rs b/extensions/pagetop-bootsier/src/theme/nav/component.rs index a0c82041..0b7797c8 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/component.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/component.rs @@ -102,28 +102,21 @@ impl Nav { self } - /// Añade un nuevo elemento hijo al menú. - pub fn add_item(mut self, item: nav::Item) -> Self { - self.items.add(Child::with(item)); - self - } - - /// Modifica la lista de elementos del menú aplicando una operación [`ChildOp`]. + /// Añade un nuevo elemento al menú o modifica la lista de elementos del menú con una operación + /// [`ChildOp`]. /// - /// Para añadir elementos usa [`Child::with(item)`](Child::with): + /// # Ejemplo /// /// ```rust,ignore - /// nav.with_items(ChildOp::Add(Child::with(nav::Item::link(...)))); - /// nav.with_items(ChildOp::AddMany(vec![ - /// Child::with(nav::Item::link(...)), - /// Child::with(nav::Item::link_disabled(...)), + /// nav.with_item(nav::Item::link("Inicio", "/")); + /// nav.with_item(ChildOp::AddMany(vec![ + /// nav::Item::link(...).into(), + /// nav::Item::link_disabled(...).into(), /// ])); /// ``` - /// - /// Para la mayoría de los casos, [`add_item()`](Self::add_item) es más directo. #[builder_fn] - pub fn with_items(mut self, op: ChildOp) -> Self { - self.items.alter_child(op); + pub fn with_item(mut self, op: impl Into) -> Self { + self.items.alter_child(op.into()); self } } diff --git a/extensions/pagetop-bootsier/src/theme/navbar.rs b/extensions/pagetop-bootsier/src/theme/navbar.rs index 717ec679..31a16ccc 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar.rs @@ -15,11 +15,11 @@ //! # use pagetop::prelude::*; //! # use pagetop_bootsier::prelude::*; //! let navbar = Navbar::simple() -//! .add_item(navbar::Item::nav( +//! .with_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .add_item(nav::Item::link(L10n::n("About"), |_| "/about".into())) -//! .add_item(nav::Item::link(L10n::n("Contact"), |_| "/contact".into())) +//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .with_item(nav::Item::link(L10n::n("About"), |_| "/about".into())) +//! .with_item(nav::Item::link(L10n::n("Contact"), |_| "/contact".into())) //! )); //! ``` //! @@ -30,11 +30,11 @@ //! # use pagetop_bootsier::prelude::*; //! let navbar = Navbar::simple_toggle() //! .with_expand(BreakPoint::MD) -//! .add_item(navbar::Item::nav( +//! .with_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .add_item(nav::Item::link_blank(L10n::n("Docs"), |_| "https://sample.com".into())) -//! .add_item(nav::Item::link(L10n::n("Support"), |_| "/support".into())) +//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .with_item(nav::Item::link_blank(L10n::n("Docs"), |_| "https://docs.rs".into())) +//! .with_item(nav::Item::link(L10n::n("Support"), |_| "/support".into())) //! )); //! ``` //! @@ -48,20 +48,20 @@ //! .with_route(Some(|cx| cx.route("/"))); //! //! let navbar = Navbar::brand_left(brand) -//! .add_item(navbar::Item::nav( +//! .with_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .add_item(nav::Item::dropdown( +//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .with_item(nav::Item::dropdown( //! Dropdown::new() //! .with_title(L10n::n("Tools")) -//! .add_item(dropdown::Item::link( +//! .with_item(dropdown::Item::link( //! L10n::n("Generator"), |_| "/tools/gen".into()) //! ) -//! .add_item(dropdown::Item::link( +//! .with_item(dropdown::Item::link( //! L10n::n("Reports"), |_| "/tools/reports".into()) //! ) //! )) -//! .add_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())) +//! .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())) //! )); //! ``` //! @@ -76,10 +76,10 @@ //! //! let navbar = Navbar::brand_right(brand) //! .with_expand(BreakPoint::LG) -//! .add_item(navbar::Item::nav( +//! .with_item(navbar::Item::nav( //! Nav::pills() -//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard".into())) -//! .add_item(nav::Item::link(L10n::n("Users"), |_| "/users".into())) +//! .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard".into())) +//! .with_item(nav::Item::link(L10n::n("Users"), |_| "/users".into())) //! )); //! ``` //! @@ -95,15 +95,15 @@ //! .with_backdrop(offcanvas::Backdrop::Enabled); //! //! let navbar = Navbar::offcanvas(oc) -//! .add_item(navbar::Item::nav( +//! .with_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .add_item(nav::Item::link(L10n::n("Profile"), |_| "/profile".into())) -//! .add_item(nav::Item::dropdown( +//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +//! .with_item(nav::Item::link(L10n::n("Profile"), |_| "/profile".into())) +//! .with_item(nav::Item::dropdown( //! Dropdown::new() //! .with_title(L10n::n("More")) -//! .add_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings".into())) -//! .add_item(dropdown::Item::link(L10n::n("Help"), |_| "/help".into())) +//! .with_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings".into())) +//! .with_item(dropdown::Item::link(L10n::n("Help"), |_| "/help".into())) //! )) //! )); //! ``` @@ -119,11 +119,11 @@ //! //! let navbar = Navbar::brand_left(brand) //! .with_position(navbar::Position::FixedTop) -//! .add_item(navbar::Item::nav( +//! .with_item(navbar::Item::nav( //! Nav::new() -//! .add_item(nav::Item::link(L10n::n("Dashboard"), |_| "/".into())) -//! .add_item(nav::Item::link(L10n::n("Donors"), |_| "/donors".into())) -//! .add_item(nav::Item::link(L10n::n("Stock"), |_| "/stock".into())) +//! .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/".into())) +//! .with_item(nav::Item::link(L10n::n("Donors"), |_| "/donors".into())) +//! .with_item(nav::Item::link(L10n::n("Stock"), |_| "/stock".into())) //! )); //! ``` diff --git a/extensions/pagetop-bootsier/src/theme/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/navbar/component.rs index 732097d4..8520bfce 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/component.rs @@ -255,29 +255,21 @@ impl Navbar { self } - /// Añade un nuevo contenido hijo. - #[inline] - pub fn add_item(mut self, item: navbar::Item) -> Self { - self.items.add(Child::with(item)); - self - } - - /// Modifica la lista de contenidos de la barra aplicando una operación [`ChildOp`]. + /// Añade un nuevo contenido a la barra de navegación o modifica la lista de contenidos de la + /// barra con una operación [`ChildOp`]. /// - /// Para añadir elementos usa [`Child::with(item)`](Child::with): + /// # Ejemplo /// /// ```rust,ignore - /// navbar.with_items(ChildOp::Add(Child::with(navbar::Item::nav(...)))); - /// navbar.with_items(ChildOp::AddMany(vec![ - /// Child::with(navbar::Item::nav(...)), - /// Child::with(navbar::Item::text(...)), + /// navbar.with_item(navbar::Item::nav(...)); + /// navbar.with_item(ChildOp::AddMany(vec![ + /// navbar::Item::nav(...).into(), + /// navbar::Item::text(...).into(), /// ])); /// ``` - /// - /// Para la mayoría de los casos, [`add_item()`](Self::add_item) es más directo. #[builder_fn] - pub fn with_items(mut self, op: ChildOp) -> Self { - self.items.alter_child(op); + pub fn with_item(mut self, op: impl Into) -> Self { + self.items.alter_child(op.into()); self } } diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas.rs b/extensions/pagetop-bootsier/src/theme/offcanvas.rs index c8b2677e..166893aa 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas.rs @@ -12,11 +12,11 @@ //! .with_backdrop(offcanvas::Backdrop::Enabled) //! .with_body_scroll(offcanvas::BodyScroll::Enabled) //! .with_visibility(offcanvas::Visibility::Default) -//! .add_child(Dropdown::new() +//! .with_child(Dropdown::new() //! .with_title(L10n::n("Menu")) -//! .add_item(dropdown::Item::label(L10n::n("Label"))) -//! .add_item(dropdown::Item::link_blank(L10n::n("Google"), |_| "https://google.es".into())) -//! .add_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout".into())) +//! .with_item(dropdown::Item::label(L10n::n("Label"))) +//! .with_item(dropdown::Item::link_blank(L10n::n("Docs"), |_| "https://docs.rs".into())) +//! .with_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout".into())) //! ); //! ``` diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs index eb582fef..93e14b27 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs @@ -135,17 +135,11 @@ impl Offcanvas { self } - /// Añade un nuevo componente hijo al panel. - #[inline] - pub fn add_child(mut self, child: impl Component) -> Self { - self.children.add(Child::with(child)); - self - } - - /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. + /// Añade un nuevo componente al panel o modifica la lista de componentes (`children`) con una + /// operación [`ChildOp`]. #[builder_fn] - pub fn with_children(mut self, op: ChildOp) -> Self { - self.children.alter_child(op); + pub fn with_child(mut self, op: impl Into) -> Self { + self.children.alter_child(op.into()); self } diff --git a/src/base/component/block.rs b/src/base/component/block.rs index e1346762..014bda4e 100644 --- a/src/base/component/block.rs +++ b/src/base/component/block.rs @@ -73,17 +73,11 @@ impl Block { self } - /// Añade un nuevo componente hijo al bloque. - #[inline] - pub fn add_child(mut self, component: impl Component) -> Self { - self.children.add(Child::with(component)); - self - } - - /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. + /// Añade un nuevo componente al bloque o modifica la lista de componentes (`children`) con una + /// operación [`ChildOp`]. #[builder_fn] - pub fn with_child(mut self, op: ChildOp) -> Self { - self.children.alter_child(op); + pub fn with_child(mut self, op: impl Into) -> Self { + self.children.alter_child(op.into()); self } } diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 060d4831..3efc76a8 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -65,10 +65,10 @@ pub enum IntroOpening { /// ```rust /// # use pagetop::prelude::*; /// let intro = Intro::default() -/// .add_child( +/// .with_child( /// Block::new() /// .with_title(L10n::l("intro_custom_block_title")) -/// .add_child(Html::with(move |cx| { +/// .with_child(Html::with(move |cx| { /// html! { /// p { (L10n::l("intro_custom_paragraph_1").using(cx)) } /// p { (L10n::l("intro_custom_paragraph_2").using(cx)) } @@ -277,19 +277,13 @@ impl Intro { self } - /// Añade un nuevo componente hijo a la intro. + /// Añade un nuevo componente a la intro o modifica la lista de componentes (`children`) con una + /// operación [`ChildOp`]. /// - /// Si es un bloque ([`Block`]) aplica estilos específicos para destacarlo. - #[inline] - pub fn add_child(mut self, component: impl Component) -> Self { - self.children.add(Child::with(component)); - self - } - - /// Modifica la lista de componentes (`children`) aplicando una operación [`ChildOp`]. + /// Si se añade un bloque ([`Block`]) se aplicarán estilos específicos para destacarlo. #[builder_fn] - pub fn with_child(mut self, op: ChildOp) -> Self { - self.children.alter_child(op); + pub fn with_child(mut self, op: impl Into) -> Self { + self.children.alter_child(op.into()); self } } diff --git a/src/base/extension/welcome.rs b/src/base/extension/welcome.rs index a3fc3777..e5c73363 100644 --- a/src/base/extension/welcome.rs +++ b/src/base/extension/welcome.rs @@ -30,22 +30,22 @@ async fn home(request: HttpRequest) -> ResultPage { Page::new(request) .with_title(L10n::l("welcome_title")) - .add_child( + .with_child( Intro::new() - .add_child( + .with_child( Block::new() .with_title(L10n::l("welcome_status_title")) - .add_child(Html::with(move |cx| { + .with_child(Html::with(move |cx| { html! { p { (L10n::l("welcome_status_1").using(cx)) } p { (L10n::l("welcome_status_2").using(cx)) } } })), ) - .add_child( + .with_child( Block::new() .with_title(L10n::l("welcome_support_title")) - .add_child(Html::with(move |cx| { + .with_child(Html::with(move |cx| { html! { p { (L10n::l("welcome_support_1").using(cx)) } p { (L10n::l("welcome_support_2").with_arg("app", app).using(cx)) } diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index 858f5d2b..34f9088b 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -24,7 +24,7 @@ impl Theme for Basic { )) .alter_child_in( &DefaultRegion::Footer, - ChildOp::AddIfEmpty(Child::with(PoweredBy::new())), + ChildOp::AddIfEmpty(PoweredBy::new().into()), ); } } diff --git a/src/core/component.rs b/src/core/component.rs index a7315147..dc31d8d8 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -9,10 +9,9 @@ mod definition; pub use definition::{Component, ComponentClone, ComponentRender}; mod children; -pub use children::Slot; pub use children::Children; pub use children::ComponentGuard; -pub use children::{Child, ChildOp}; +pub use children::{Child, ChildOp, Slot}; mod message; pub use message::{MessageLevel, StatusMessage}; diff --git a/src/core/component/children.rs b/src/core/component/children.rs index 4eb29d6d..2379f42e 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -75,9 +75,9 @@ impl From> for Child { /// Útil cuando se tiene un [`Slot`] y se necesita añadirlo a una lista [`Children`]: /// /// ```rust,ignore - /// children.add(Child::from(my_slot)); + /// children.with_child(Child::from(my_slot)); /// // o equivalentemente: - /// children.add(my_slot.into()); + /// children.with_child(my_slot.into()); /// ``` fn from(typed: Slot) -> Self { if let Some(m) = typed.0 { @@ -97,16 +97,33 @@ impl From for Child { } } +impl From for ChildOp { + /// Convierte un componente en [`ChildOp::Add`], permitiendo pasar componentes directamente a + /// métodos como [`Children::with_child`] sin envolverlos explícitamente. + #[inline] + fn from(component: T) -> Self { + ChildOp::Add(Child::with(component)) + } +} + +impl From for ChildOp { + /// Convierte un [`Child`] en [`ChildOp::Add`]. + #[inline] + fn from(child: Child) -> Self { + ChildOp::Add(child) + } +} + // ************************************************************************************************* -/// Variante tipada de [`Child`] para componentes con un tipo concreto conocido. +/// Contenedor tipado para un *único* componente de un tipo concreto conocido. /// /// A diferencia de [`Child`], que encapsula cualquier componente como `dyn Component`, `Slot` /// mantiene el tipo concreto `C` y permite acceder directamente a sus métodos específicos a través /// de [`get()`](Slot::get). /// -/// Se utiliza habitualmente para incrustar un componente dentro de otro cuando no se necesita una -/// lista completa de hijos ([`Children`]), sino un único componente tipado en un campo concreto. +/// Se usa habitualmente para incluir un componente dentro de otro cuando no se necesita una lista +/// completa de hijos ([`Children`]), sino un único componente tipado en un campo concreto. #[derive(AutoDefault)] pub struct Slot(Option>); @@ -194,23 +211,59 @@ impl Slot { /// Operaciones para componentes hijo [`Child`] en una lista [`Children`]. pub enum ChildOp { + /// Añade un hijo al final de la lista. Add(Child), + /// Añade un hijo solo si la lista está vacía. AddIfEmpty(Child), + /// Añade varios hijos al final de la lista, en el orden recibido. AddMany(Vec), + /// Inserta un hijo justo después del componente con el `id` dado, o al final si no existe. InsertAfterId(&'static str, Child), + /// Inserta un hijo justo antes del componente con el `id` dado, o al principio si no existe. InsertBeforeId(&'static str, Child), + /// Inserta un hijo al principio de la lista. Prepend(Child), + /// Inserta varios hijos al principio de la lista, manteniendo el orden recibido. PrependMany(Vec), + /// Elimina el primer hijo con el `id` dado. RemoveById(&'static str), + /// Sustituye el primer hijo con el `id` dado por otro componente. ReplaceById(&'static str, Child), + /// Vacía la lista eliminando todos los hijos. Reset, } /// Lista ordenada de componentes hijo ([`Child`]) mantenida por un componente padre. /// -/// Esta lista permite añadir, modificar, renderizar y consultar componentes hijo en orden de -/// inserción, soportando operaciones avanzadas como inserción relativa o reemplazo por -/// identificador. +/// Permite añadir, modificar, renderizar y consultar componentes hijo en orden de inserción, con +/// soporte para operaciones avanzadas como inserción relativa o reemplazo por identificador a +/// través de [`ChildOp`]. +/// +/// Los tipos que completan este sistema son: +/// +/// - [`Child`]: representa un componente hijo encapsulado dentro de la lista. Almacena cualquier +/// componente sin necesidad de conocer su tipo concreto. +/// - [`Slot`]: contenedor tipado para un *único* componente de tipo `C`. Preferible a `Children` +/// cuando el padre solo necesita un componente y quiere acceso directo a los métodos de `C`. +/// - [`ChildOp`]: operaciones disponibles sobre la lista. Cuando se necesita algo más que añadir al +/// final, se construye la variante adecuada y se pasa a [`with_child`](Self::with_child). +/// - [`ComponentGuard`]: devuelto por [`Slot::get`] para garantizar acceso exclusivo al componente +/// tipado. Mientras está activo bloquea cualquier otro acceso por lo que conviene liberarlo +/// cuanto antes. +/// +/// # Conversiones implícitas +/// +/// Cualquier componente implementa `Into` (equivalente a `ChildOp::Add`) e `Into`. +/// Gracias a esto, [`with_child`](Self::with_child) acepta un componente directamente o cualquier +/// variante de [`ChildOp`]: +/// +/// ```rust,ignore +/// // Añadir al final de la lista (implícito): +/// children.with_child(MiComponente::new()); +/// +/// // Operación explícita: +/// children.with_child(ChildOp::Prepend(MiComponente::new().into())); +/// ``` #[derive(AutoDefault, Clone, Debug)] pub struct Children(Vec); @@ -222,15 +275,15 @@ impl Children { /// Crea una lista con un componente hijo inicial. pub fn with(child: Child) -> Self { - Self::default().with_child(ChildOp::Add(child)) + Self::default().with_child(child) } // **< Children BUILDER >*********************************************************************** - /// Ejecuta una operación con [`ChildOp`] en la lista. + /// Añade un componente hijo o aplica una operación [`ChildOp`] sobre la lista. #[builder_fn] - pub fn with_child(mut self, op: ChildOp) -> Self { - match op { + pub fn with_child(mut self, op: impl Into) -> Self { + match op.into() { ChildOp::Add(any) => self.add(any), ChildOp::AddIfEmpty(any) => self.add_if_empty(any), ChildOp::AddMany(many) => self.add_many(many), @@ -245,17 +298,15 @@ impl Children { } /// Añade un componente hijo al final de la lista. - /// - /// Es un atajo para `children.alter_child(ChildOp::Add(child))`. #[inline] - pub fn add(&mut self, child: Child) -> &mut Self { + pub(crate) fn add(&mut self, child: Child) -> &mut Self { self.0.push(child); self } /// Añade un componente hijo en la lista sólo si está vacía. #[inline] - pub fn add_if_empty(&mut self, child: Child) -> &mut Self { + pub(crate) fn add_if_empty(&mut self, child: Child) -> &mut Self { if self.0.is_empty() { self.0.push(child); } @@ -336,7 +387,7 @@ impl Children { self } - /// Inserta un hijo al principio de la colección. + /// Inserta un hijo al principio de la lista. #[inline] fn prepend(&mut self, child: Child) -> &mut Self { self.0.insert(0, child); @@ -391,7 +442,7 @@ impl IntoIterator for Children { /// Consume la estructura `Children`, devolviendo un iterador que consume los elementos. /// - /// # Ejemplo de uso: + /// # Ejemplo /// /// ```rust,ignore /// let children = Children::new().with(child1).with(child2); @@ -410,7 +461,7 @@ impl<'a> IntoIterator for &'a Children { /// Itera sobre una referencia inmutable de `Children`, devolviendo un iterador de referencia. /// - /// # Ejemplo de uso: + /// # Ejemplo /// /// ```rust,ignore /// let children = Children::new().with(child1).with(child2); @@ -429,7 +480,7 @@ impl<'a> IntoIterator for &'a mut Children { /// Itera sobre una referencia mutable de `Children`, devolviendo un iterador mutable. /// - /// # Ejemplo de uso: + /// # Ejemplo /// /// ```rust,ignore /// let mut children = Children::new().with(child1).with(child2); diff --git a/src/core/component/context.rs b/src/core/component/context.rs index 24d290e2..e33ada2b 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -1,6 +1,6 @@ use crate::core::component::{ChildOp, MessageLevel, StatusMessage}; use crate::core::theme::all::DEFAULT_THEME; -use crate::core::theme::{ChildrenInRegions, RegionRef, TemplateRef, ThemeRef}; +use crate::core::theme::{ChildrenInRegions, DefaultRegion, RegionRef, TemplateRef, ThemeRef}; use crate::core::TypeInfo; use crate::html::{html, Markup, RoutePath}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; @@ -137,9 +137,15 @@ pub trait Contextual: LangId { #[builder_fn] fn with_assets(self, op: AssetsOp) -> Self; - /// Opera con [`ChildOp`] en una región del documento. + /// Añade un componente o aplica una operación [`ChildOp`] en la región por defecto del + /// documento. #[builder_fn] - fn with_child_in(self, region_ref: RegionRef, op: ChildOp) -> Self; + fn with_child(self, op: impl Into) -> Self; + + /// Añade un componente o aplica una operación [`ChildOp`] en una región específica del + /// documento. + #[builder_fn] + fn with_child_in(self, region_ref: RegionRef, op: impl Into) -> Self; // **< Contextual GETTERS >********************************************************************* @@ -557,8 +563,15 @@ impl Contextual for Context { } #[builder_fn] - fn with_child_in(mut self, region_ref: RegionRef, op: ChildOp) -> Self { - self.regions.alter_child_in(region_ref, op); + fn with_child(mut self, op: impl Into) -> Self { + self.regions + .alter_child_in(&DefaultRegion::Content, op.into()); + self + } + + #[builder_fn] + fn with_child_in(mut self, region_ref: RegionRef, op: impl Into) -> Self { + self.regions.alter_child_in(region_ref, op.into()); self } diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 3aea7ca3..e17173bb 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -1,5 +1,5 @@ use crate::base::component::{Html, Intro, IntroOpening}; -use crate::core::component::{Child, ChildOp, Component, ComponentError, Context, Contextual}; +use crate::core::component::{ChildOp, Component, ComponentError, Context, Contextual}; use crate::core::extension::Extension; use crate::core::theme::{DefaultRegion, DefaultTemplate, TemplateRef}; use crate::global; @@ -247,14 +247,17 @@ pub trait Theme: Extension + Send + Sync { .alter_template(&DefaultTemplate::Error) .alter_child_in( &DefaultRegion::Content, - ChildOp::Prepend(Child::with(Html::with(move |cx| { - html! { - div { - h1 { (L10n::l("error403_alert").using(cx)) } - p { (L10n::l("error403_help").using(cx)) } + ChildOp::Prepend( + Html::with(move |cx| { + html! { + div { + h1 { (L10n::l("error403_alert").using(cx)) } + p { (L10n::l("error403_help").using(cx)) } + } } - } - }))), + }) + .into(), + ), ); } @@ -270,14 +273,17 @@ pub trait Theme: Extension + Send + Sync { .alter_template(&DefaultTemplate::Error) .alter_child_in( &DefaultRegion::Content, - ChildOp::Prepend(Child::with(Html::with(move |cx| { - html! { - div { - h1 { (L10n::l("error404_alert").using(cx)) } - p { (L10n::l("error404_help").using(cx)) } + ChildOp::Prepend( + Html::with(move |cx| { + html! { + div { + h1 { (L10n::l("error404_alert").using(cx)) } + p { (L10n::l("error404_help").using(cx)) } + } } - } - }))), + }) + .into(), + ), ); } @@ -300,19 +306,20 @@ pub trait Theme: Extension + Send + Sync { .alter_template(&DefaultTemplate::Error) .alter_child_in( &DefaultRegion::Content, - ChildOp::Prepend(Child::with( + ChildOp::Prepend( Intro::new() .with_title(L10n::l("error_code").with_arg("code", code.to_string())) .with_slogan(L10n::n(code.to_string())) .with_button(None) .with_opening(IntroOpening::Custom) - .add_child(Html::with(move |cx| { + .with_child(Html::with(move |cx| { html! { h1 { (alert.using(cx)) } p { (help.using(cx)) } } - })), - )), + })) + .into(), + ), ); } } diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index 2b6089a6..a2b71ff2 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -44,16 +44,19 @@ pub(crate) struct ChildrenInRegions(HashMap); impl ChildrenInRegions { pub fn with(region_ref: RegionRef, child: Child) -> Self { - Self::default().with_child_in(region_ref, ChildOp::Add(child)) + Self::default().with_child_in(region_ref, child) } #[builder_fn] - pub fn with_child_in(mut self, region_ref: RegionRef, op: ChildOp) -> Self { + pub fn with_child_in(mut self, region_ref: RegionRef, op: impl Into) -> Self { + let child = op.into(); if let Some(region) = self.0.get_mut(region_ref.name()) { - region.alter_child(op); + region.alter_child(child); } else { - self.0 - .insert(region_ref.name().to_owned(), Children::new().with_child(op)); + self.0.insert( + region_ref.name().to_owned(), + Children::new().with_child(child), + ); } self } diff --git a/src/lib.rs b/src/lib.rs index 3e84a03e..0213e61e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,7 @@ impl Extension for HelloWorld { async fn hello_world(request: HttpRequest) -> ResultPage { Page::new(request) - .add_child(Html::with(|_| html! { h1 { "Hello World!" } })) + .with_child(Html::with(|_| html! { h1 { "Hello World!" } })) .render() } diff --git a/src/response/page.rs b/src/response/page.rs index 9400d9e4..3e1f1bf2 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -19,8 +19,7 @@ pub use error::ErrorPage; pub use actix_web::Result as ResultPage; use crate::base::action; -use crate::core::component::{AssetsOp, Context, Contextual}; -use crate::core::component::{Child, ChildOp, Component}; +use crate::core::component::{AssetsOp, ChildOp, Context, Contextual}; use crate::core::theme::{DefaultRegion, Region, RegionRef, TemplateRef, ThemeRef}; use crate::html::{html, Markup, DOCTYPE}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; @@ -160,22 +159,6 @@ impl Page { self } - /// Añade un componente hijo a la región de contenido por defecto. - pub fn add_child(mut self, component: impl Component) -> Self { - self.context.alter_child_in( - &DefaultRegion::Content, - ChildOp::Add(Child::with(component)), - ); - self - } - - /// Añade un componente hijo en la región `region_name` de la página. - pub fn add_child_in(mut self, region_ref: RegionRef, component: impl Component) -> Self { - self.context - .alter_child_in(region_ref, ChildOp::Add(Child::with(component))); - self - } - // **< Page GETTERS >*************************************************************************** /// Devuelve el título traducido para el idioma de la página, si existe. @@ -340,8 +323,15 @@ impl Contextual for Page { } #[builder_fn] - fn with_child_in(mut self, region_ref: RegionRef, op: ChildOp) -> Self { - self.context.alter_child_in(region_ref, op); + fn with_child(mut self, op: impl Into) -> Self { + self.context + .alter_child_in(&DefaultRegion::Content, op.into()); + self + } + + #[builder_fn] + fn with_child_in(mut self, region_ref: RegionRef, op: impl Into) -> Self { + self.context.alter_child_in(region_ref, op.into()); self } diff --git a/tests/component_children.rs b/tests/component_children.rs index 36ca23a4..c27ad4af 100644 --- a/tests/component_children.rs +++ b/tests/component_children.rs @@ -66,7 +66,10 @@ async fn child_id_returns_component_id() { #[pagetop::test] async fn child_from_component_is_equivalent_to_with() { let child: Child = TestComp::text("desde from").into(); - assert_eq!(child.render(&mut Context::default()).into_string(), "desde from"); + assert_eq!( + child.render(&mut Context::default()).into_string(), + "desde from" + ); } #[pagetop::test] @@ -74,8 +77,14 @@ async fn child_clone_is_deep() { // Modificar el clon no debe afectar al original. let original = Child::with(TestComp::text("original")); let clone = original.clone(); - assert_eq!(original.render(&mut Context::default()).into_string(), "original"); - assert_eq!(clone.render(&mut Context::default()).into_string(), "original"); + assert_eq!( + original.render(&mut Context::default()).into_string(), + "original" + ); + assert_eq!( + clone.render(&mut Context::default()).into_string(), + "original" + ); } // **< Children + ChildOp >************************************************************************* @@ -90,9 +99,9 @@ async fn children_new_is_empty() { #[pagetop::test] async fn children_add_appends_in_order() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::text("a")))) - .with_child(ChildOp::Add(Child::with(TestComp::text("b")))) - .with_child(ChildOp::Add(Child::with(TestComp::text("c")))); + .with_child(TestComp::text("a")) + .with_child(TestComp::text("b")) + .with_child(TestComp::text("c")); assert_eq!(c.len(), 3); assert_eq!(c.render(&mut Context::default()).into_string(), "abc"); } @@ -102,12 +111,11 @@ async fn children_add_if_empty_only_adds_when_list_is_empty() { let mut cx = Context::default(); // Se añade porque la lista está vacía. - let c = Children::new() - .with_child(ChildOp::AddIfEmpty(Child::with(TestComp::text("primero")))); + let c = Children::new().with_child(ChildOp::AddIfEmpty(TestComp::text("primero").into())); assert_eq!(c.len(), 1); // No se añade porque ya hay un elemento. - let c = c.with_child(ChildOp::AddIfEmpty(Child::with(TestComp::text("segundo")))); + let c = c.with_child(ChildOp::AddIfEmpty(TestComp::text("segundo").into())); assert_eq!(c.len(), 1); assert_eq!(c.render(&mut cx).into_string(), "primero"); } @@ -115,9 +123,9 @@ async fn children_add_if_empty_only_adds_when_list_is_empty() { #[pagetop::test] async fn children_add_many_appends_all_in_order() { let c = Children::new().with_child(ChildOp::AddMany(vec![ - Child::with(TestComp::text("x")), - Child::with(TestComp::text("y")), - Child::with(TestComp::text("z")), + TestComp::text("x").into(), + TestComp::text("y").into(), + TestComp::text("z").into(), ])); assert_eq!(c.len(), 3); assert_eq!(c.render(&mut Context::default()).into_string(), "xyz"); @@ -126,18 +134,18 @@ async fn children_add_many_appends_all_in_order() { #[pagetop::test] async fn children_prepend_inserts_at_start() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::text("b")))) - .with_child(ChildOp::Prepend(Child::with(TestComp::text("a")))); + .with_child(TestComp::text("b")) + .with_child(ChildOp::Prepend(TestComp::text("a").into())); assert_eq!(c.render(&mut Context::default()).into_string(), "ab"); } #[pagetop::test] async fn children_prepend_many_inserts_all_at_start_maintaining_order() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::text("c")))) + .with_child(TestComp::text("c")) .with_child(ChildOp::PrependMany(vec![ - Child::with(TestComp::text("a")), - Child::with(TestComp::text("b")), + TestComp::text("a").into(), + TestComp::text("b").into(), ])); assert_eq!(c.render(&mut Context::default()).into_string(), "abc"); } @@ -145,43 +153,49 @@ async fn children_prepend_many_inserts_all_at_start_maintaining_order() { #[pagetop::test] async fn children_insert_after_id_inserts_after_matching_element() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::tagged("first", "a")))) - .with_child(ChildOp::Add(Child::with(TestComp::text("c")))) - .with_child(ChildOp::InsertAfterId("first", Child::with(TestComp::text("b")))); + .with_child(TestComp::tagged("first", "a")) + .with_child(TestComp::text("c")) + .with_child(ChildOp::InsertAfterId("first", TestComp::text("b").into())); assert_eq!(c.render(&mut Context::default()).into_string(), "abc"); } #[pagetop::test] async fn children_insert_after_id_appends_when_id_not_found() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::text("a")))) - .with_child(ChildOp::InsertAfterId("no-existe", Child::with(TestComp::text("b")))); + .with_child(TestComp::text("a")) + .with_child(ChildOp::InsertAfterId( + "no-existe", + TestComp::text("b").into(), + )); assert_eq!(c.render(&mut Context::default()).into_string(), "ab"); } #[pagetop::test] async fn children_insert_before_id_inserts_before_matching_element() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::text("a")))) - .with_child(ChildOp::Add(Child::with(TestComp::tagged("last", "c")))) - .with_child(ChildOp::InsertBeforeId("last", Child::with(TestComp::text("b")))); + .with_child(TestComp::text("a")) + .with_child(TestComp::tagged("last", "c")) + .with_child(ChildOp::InsertBeforeId("last", TestComp::text("b").into())); assert_eq!(c.render(&mut Context::default()).into_string(), "abc"); } #[pagetop::test] async fn children_insert_before_id_prepends_when_id_not_found() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::text("b")))) - .with_child(ChildOp::InsertBeforeId("no-existe", Child::with(TestComp::text("a")))); + .with_child(TestComp::text("b")) + .with_child(ChildOp::InsertBeforeId( + "no-existe", + TestComp::text("a").into(), + )); assert_eq!(c.render(&mut Context::default()).into_string(), "ab"); } #[pagetop::test] async fn children_remove_by_id_removes_first_matching_element() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::tagged("keep", "a")))) - .with_child(ChildOp::Add(Child::with(TestComp::tagged("drop", "b")))) - .with_child(ChildOp::Add(Child::with(TestComp::text("c")))) + .with_child(TestComp::tagged("keep", "a")) + .with_child(TestComp::tagged("drop", "b")) + .with_child(TestComp::text("c")) .with_child(ChildOp::RemoveById("drop")); assert_eq!(c.len(), 2); assert_eq!(c.render(&mut Context::default()).into_string(), "ac"); @@ -190,7 +204,7 @@ async fn children_remove_by_id_removes_first_matching_element() { #[pagetop::test] async fn children_remove_by_id_does_nothing_when_id_not_found() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::text("a")))) + .with_child(TestComp::text("a")) .with_child(ChildOp::RemoveById("no-existe")); assert_eq!(c.len(), 1); } @@ -198,11 +212,11 @@ async fn children_remove_by_id_does_nothing_when_id_not_found() { #[pagetop::test] async fn children_replace_by_id_replaces_first_matching_element() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::tagged("target", "viejo")))) - .with_child(ChildOp::Add(Child::with(TestComp::text("b")))) + .with_child(TestComp::tagged("target", "viejo")) + .with_child(TestComp::text("b")) .with_child(ChildOp::ReplaceById( "target", - Child::with(TestComp::text("nuevo")), + TestComp::text("nuevo").into(), )); assert_eq!(c.len(), 2); assert_eq!(c.render(&mut Context::default()).into_string(), "nuevob"); @@ -211,8 +225,8 @@ async fn children_replace_by_id_replaces_first_matching_element() { #[pagetop::test] async fn children_reset_clears_all_elements() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::text("a")))) - .with_child(ChildOp::Add(Child::with(TestComp::text("b")))) + .with_child(TestComp::text("a")) + .with_child(TestComp::text("b")) .with_child(ChildOp::Reset); assert!(c.is_empty()); } @@ -220,8 +234,8 @@ async fn children_reset_clears_all_elements() { #[pagetop::test] async fn children_get_by_id_returns_first_matching_child() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::tagged("uno", "a")))) - .with_child(ChildOp::Add(Child::with(TestComp::tagged("dos", "b")))); + .with_child(TestComp::tagged("uno", "a")) + .with_child(TestComp::tagged("dos", "b")); assert!(c.get_by_id("uno").is_some()); assert!(c.get_by_id("dos").is_some()); assert!(c.get_by_id("tres").is_none()); @@ -230,9 +244,9 @@ async fn children_get_by_id_returns_first_matching_child() { #[pagetop::test] async fn children_iter_by_id_yields_all_matching_children() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::tagged("rep", "a")))) - .with_child(ChildOp::Add(Child::with(TestComp::tagged("rep", "b")))) - .with_child(ChildOp::Add(Child::with(TestComp::tagged("otro", "c")))); + .with_child(TestComp::tagged("rep", "a")) + .with_child(TestComp::tagged("rep", "b")) + .with_child(TestComp::tagged("otro", "c")); assert_eq!(c.iter_by_id("rep").count(), 2); assert_eq!(c.iter_by_id("otro").count(), 1); assert_eq!(c.iter_by_id("ninguno").count(), 0); @@ -241,10 +255,13 @@ async fn children_iter_by_id_yields_all_matching_children() { #[pagetop::test] async fn children_render_concatenates_all_outputs_in_order() { let c = Children::new() - .with_child(ChildOp::Add(Child::with(TestComp::text("uno ")))) - .with_child(ChildOp::Add(Child::with(TestComp::text("dos ")))) - .with_child(ChildOp::Add(Child::with(TestComp::text("tres")))); - assert_eq!(c.render(&mut Context::default()).into_string(), "uno dos tres"); + .with_child(TestComp::text("uno ")) + .with_child(TestComp::text("dos ")) + .with_child(TestComp::text("tres")); + assert_eq!( + c.render(&mut Context::default()).into_string(), + "uno dos tres" + ); } // **< Slot >**************************************************************************************** @@ -261,7 +278,10 @@ async fn slot_default_is_empty() { async fn slot_with_stores_component() { let slot = Slot::with(TestComp::text("contenido")); assert!(slot.get().is_some()); - assert_eq!(slot.render(&mut Context::default()).into_string(), "contenido"); + assert_eq!( + slot.render(&mut Context::default()).into_string(), + "contenido" + ); } #[pagetop::test] @@ -290,9 +310,12 @@ async fn slot_get_allows_mutating_component() { #[pagetop::test] async fn slot_with_component_replaces_content() { - let slot = Slot::with(TestComp::text("primero")) - .with_component(Some(TestComp::text("segundo"))); - assert_eq!(slot.render(&mut Context::default()).into_string(), "segundo"); + let slot = + Slot::with(TestComp::text("primero")).with_component(Some(TestComp::text("segundo"))); + assert_eq!( + slot.render(&mut Context::default()).into_string(), + "segundo" + ); } #[pagetop::test] @@ -318,5 +341,8 @@ async fn slot_clone_is_deep() { async fn slot_converts_into_child() { let slot = Slot::with(TestComp::text("desde slot")); let child = Child::from(slot); - assert_eq!(child.render(&mut Context::default()).into_string(), "desde slot"); + assert_eq!( + child.render(&mut Context::default()).into_string(), + "desde slot" + ); }