Compare commits

..

3 commits

Author SHA1 Message Date
4ba66f5995 🚧 (base): Añade BadgeKind para el tipo de Badge 2026-08-21 11:18:16 +02:00
997017bb75 🎨 (core): Retoques de estilo menores 2026-08-21 08:23:39 +02:00
b50d186fb1 (theme): Añade IntoColor para colores opcionales
Permite que métodos como `with_color(color: impl IntoColor)` acepten un
color directo o uno opcional (`Some(...)`/`None`) sin la ambigüedad que
produce un `Into<Option<C>>` genérico.
2026-08-21 08:18:28 +02:00
19 changed files with 283 additions and 67 deletions

View file

@ -44,6 +44,32 @@ body {
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
} }
/*
* Badge component
*/
.badge {
display: inline-block;
padding: 0.3em 0.6em;
font-size: 0.8125rem;
font-weight: 600;
line-height: 1;
color: #fff;
background-color: var(--val-color--secondary);
border-radius: 0.375rem;
white-space: nowrap;
}
.badge-info,
.badge-warning {
color: #000;
}
.badge-primary { background-color: var(--val-color--primary); }
.badge-secondary { background-color: var(--val-color--secondary); }
.badge-success { background-color: var(--val-color--success); }
.badge-info { background-color: var(--val-color--info); }
.badge-warning { background-color: var(--val-color--warning); }
.badge-danger { background-color: var(--val-color--danger); }
/* /*
* Form components * Form components
*/ */

View file

@ -2,7 +2,7 @@
// Badge. // Badge.
pub(crate) mod badge; pub(crate) mod badge;
pub use badge::{Badge, BadgeBootsier}; pub use badge::{Badge, BadgeKind};
// Button. // Button.
mod button; mod button;

View file

@ -1,45 +1,18 @@
use pagetop::prelude::*; use pagetop::prelude::*;
use crate::theme::*; pub use pagetop::base::component::{Badge, BadgeKind};
pub use pagetop::base::component::Badge;
const EXTRA_TEXT_BG: &str = "bootsier.badge.text_bg";
/// Extensión de Bootsier para [`Badge`].
///
/// Proporciona el método [`with_text_bg()`](Self::with_text_bg) para fijar el color de fondo del
/// badge usando un color de texto con contraste suficiente garantizado.
///
/// ```rust,no_run
/// use pagetop::prelude::*;
/// use pagetop_bootsier::theme::*;
///
/// let admin = bs::Badge::labeled(Lc::n("Admin")).with_text_bg(ThemeColor::Danger);
/// ```
pub trait BadgeBootsier {
#[builder_fn]
fn with_text_bg(self, color: ThemeColor) -> Self;
}
impl BadgeBootsier for Badge {
/// Establece el color de fondo usando un color de texto con contraste garantizado.
///
/// Igual a `with_prop(PropsOp::add_classes(class::TextColor::Bg(color)))`, pero sin el riesgo
/// de acumular más de una clase `text-bg-{color}` si se llama varias veces.
#[builder_fn]
fn with_text_bg(mut self, color: ThemeColor) -> Self {
self.alter_prop(PropsOp::set_extra(EXTRA_TEXT_BG, color));
self
}
}
// **< Badge SETUP >******************************************************************************** // **< Badge SETUP >********************************************************************************
#[rustfmt::skip]
pub(crate) fn setup(badge: &mut Badge) { pub(crate) fn setup(badge: &mut Badge) {
let color = badge.props().extra_or(EXTRA_TEXT_BG, ThemeColor::Secondary); let (old, new) = match badge.kind() {
badge.alter_prop(PropsOp::replace_classes( BadgeKind::Primary => ("badge-primary", "text-bg-primary"),
"badge", BadgeKind::Secondary => ("badge-secondary", "text-bg-secondary"),
util::join!("badge ", class::TextColor::Bg(color).to_class()), BadgeKind::Success => ("badge-success", "text-bg-success"),
)); BadgeKind::Info => ("badge-info", "text-bg-info"),
BadgeKind::Warning => ("badge-warning", "text-bg-warning"),
BadgeKind::Danger => ("badge-danger", "text-bg-danger"),
};
badge.alter_prop(PropsOp::replace_classes(old, new));
} }

View file

@ -3,7 +3,7 @@
pub mod layout; pub mod layout;
mod badge; mod badge;
pub use badge::Badge; pub use badge::{Badge, BadgeKind};
mod brand; mod brand;
pub use brand::Brand; pub use brand::Brand;

View file

@ -1,12 +1,31 @@
use crate::prelude::*; use crate::prelude::*;
// **< BadgeKind >**********************************************************************************
/// Tipo de [`Badge`].
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
pub enum BadgeKind {
Primary,
#[default]
Secondary,
Success,
Info,
Warning,
Danger,
}
// **< Badge >**************************************************************************************
/// Componente para mostrar una **etiqueta corta informativa** (*badge*). /// Componente para mostrar una **etiqueta corta informativa** (*badge*).
/// ///
/// # Ejemplo /// # Ejemplo
/// ///
/// ```rust,no_run /// ```rust,no_run
/// # use pagetop::prelude::*; /// # use pagetop::prelude::*;
/// let badge = Badge::labeled(Lc::n("Admin")); /// let badge = Badge::labeled(Lc::n("Admin")).with_kind(BadgeKind::Danger);
///
/// // Equivalente usando el constructor directo del tipo.
/// let badge = Badge::danger(Lc::n("Admin"));
/// ``` /// ```
#[derive(AutoDefault, Clone, Debug, Getters)] #[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Badge { pub struct Badge {
@ -14,6 +33,9 @@ pub struct Badge {
props: Props, props: Props,
/// Devuelve la etiqueta del badge. /// Devuelve la etiqueta del badge.
label: Lc, label: Lc,
/// Devuelve el tipo del badge.
#[getters(copy)]
kind: BadgeKind,
} }
#[async_trait] #[async_trait]
@ -26,8 +48,16 @@ impl Component for Badge {
self.props.get_id() self.props.get_id()
} }
#[rustfmt::skip]
fn setup(&mut self, _cx: &Context) { fn setup(&mut self, _cx: &Context) {
self.alter_prop(PropsOp::prepend_classes("badge")); self.alter_prop(PropsOp::prepend_classes(match self.kind() {
BadgeKind::Primary => "badge badge-primary",
BadgeKind::Secondary => "badge badge-secondary",
BadgeKind::Success => "badge badge-success",
BadgeKind::Info => "badge badge-info",
BadgeKind::Warning => "badge badge-warning",
BadgeKind::Danger => "badge badge-danger",
}));
} }
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> { async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
@ -40,7 +70,7 @@ impl Component for Badge {
} }
impl Badge { impl Badge {
/// Crea un badge a partir de la etiqueta indicada. /// Crea un badge predeterminado (`BadgeKind::default()`) con la etiqueta indicada.
pub fn labeled(label: Lc) -> Self { pub fn labeled(label: Lc) -> Self {
Self { Self {
label, label,
@ -48,6 +78,60 @@ impl Badge {
} }
} }
/// Crea un badge de tipo *primary* con la etiqueta indicada.
pub fn primary(label: Lc) -> Self {
Self {
label,
kind: BadgeKind::Primary,
..Default::default()
}
}
/// Crea un badge de tipo *secondary* con la etiqueta indicada.
pub fn secondary(label: Lc) -> Self {
Self {
label,
kind: BadgeKind::Secondary,
..Default::default()
}
}
/// Crea un badge de tipo *success* con la etiqueta indicada.
pub fn success(label: Lc) -> Self {
Self {
label,
kind: BadgeKind::Success,
..Default::default()
}
}
/// Crea un badge de tipo *info* con la etiqueta indicada.
pub fn info(label: Lc) -> Self {
Self {
label,
kind: BadgeKind::Info,
..Default::default()
}
}
/// Crea un badge de tipo *warning* con la etiqueta indicada.
pub fn warning(label: Lc) -> Self {
Self {
label,
kind: BadgeKind::Warning,
..Default::default()
}
}
/// Crea un badge de tipo *danger* con la etiqueta indicada.
pub fn danger(label: Lc) -> Self {
Self {
label,
kind: BadgeKind::Danger,
..Default::default()
}
}
// **< Badge BUILDER >************************************************************************** // **< Badge BUILDER >**************************************************************************
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
@ -70,4 +154,11 @@ impl Badge {
self.label = label; self.label = label;
self self
} }
/// Establece el tipo del badge.
#[builder_fn]
pub fn with_kind(mut self, kind: BadgeKind) -> Self {
self.kind = kind;
self
}
} }

View file

@ -109,7 +109,7 @@ impl TypeInfo {
/// Proporciona información de tipo en tiempo de ejecución y conversión dinámica de tipos. /// Proporciona información de tipo en tiempo de ejecución y conversión dinámica de tipos.
/// ///
/// Este *trait* se implementa automáticamente para **todos** los tipos que implementen [`Any`], de /// Este trait se implementa automáticamente para **todos** los tipos que implementen [`Any`], de
/// modo que basta con traer [`AnyInfo`] al ámbito (`use crate::AnyInfo;`) para disponer de estos /// modo que basta con traer [`AnyInfo`] al ámbito (`use crate::AnyInfo;`) para disponer de estos
/// métodos adicionales, o usar el [`prelude`](crate::prelude) de PageTop. /// métodos adicionales, o usar el [`prelude`](crate::prelude) de PageTop.
/// ///

View file

@ -18,7 +18,7 @@ pub trait ComponentClone {
/// Define la función de renderizado para todos los componentes. /// Define la función de renderizado para todos los componentes.
/// ///
/// Este *trait* se implementa automáticamente en cualquier tipo (componente) que implemente /// Este trait se implementa automáticamente en cualquier tipo (componente) que implemente
/// [`Component`], por lo que no requiere ninguna codificación manual. /// [`Component`], por lo que no requiere ninguna codificación manual.
#[async_trait] #[async_trait]
pub trait ComponentRender { pub trait ComponentRender {

View file

@ -8,7 +8,7 @@ use crate::web::Router;
/// Interfaz común que debe implementar cualquier extensión de PageTop. /// Interfaz común que debe implementar cualquier extensión de PageTop.
/// ///
/// Este *trait* es fácil de implementar, basta con declarar una estructura sin campos para la /// Este trait es fácil de implementar, basta con declarar una estructura sin campos para la
/// extensión y sobrescribir los métodos que sean necesarios. Por ejemplo: /// extensión y sobrescribir los métodos que sean necesarios. Por ejemplo:
/// ///
/// ```rust,no_run /// ```rust,no_run

View file

@ -118,7 +118,7 @@
//! [`ReservedRegions`]: crate::response::ReservedRegions //! [`ReservedRegions`]: crate::response::ReservedRegions
mod color; mod color;
pub use color::{ColorName, CoreColors}; pub use color::{ColorName, CoreColors, IntoColor};
mod layout; mod layout;
pub use layout::{CoreRegions, RegionName, RegionRef}; pub use layout::{CoreRegions, RegionName, RegionRef};

View file

@ -5,13 +5,41 @@ use crate::AutoDefault;
/// Interfaz común para los colores de la paleta de un tema. /// Interfaz común para los colores de la paleta de un tema.
/// ///
/// PageTop ofrece una implementación predeterminada en [`CoreColors`], aunque probablemente cada /// PageTop ofrece una implementación predeterminada en [`CoreColors`], aunque probablemente cada
/// tema proporcionará su propia lista de colores implementando este *trait*. /// tema proporcionará su propia lista de colores implementando este trait.
pub trait ColorName { pub trait ColorName {
/// Devuelve el nombre asociado al color (p. ej. `"primary"`, `"danger"`, etc.). Normalmente se /// Devuelve el nombre asociado al color (p. ej. `"primary"`, `"danger"`, etc.). Normalmente se
/// usará para generar la clase CSS del componente. /// usará para generar la clase CSS del componente.
fn name(self) -> &'static str; fn name(self) -> &'static str;
} }
// **< IntoColor >**********************************************************************************
/// Convierte un color, o su ausencia, en el nombre ya resuelto.
///
/// Permite que un método como `with_color(color: impl IntoColor)` acepte indistintamente un color
/// directo (`CoreColors::Danger`) o uno opcional (`Some(CoreColors::Danger)`, o `None` para no
/// aplicar ninguno).
///
/// Evita la ambigüedad de `impl<T> From<T> for T` junto con `impl<T> From<T> for Option<T>` al
/// resolver un `C` genérico acotado por [`ColorName`]. Al ser [`IntoColor`] un trait propio,
/// ninguna implementación de [`ColorName`] cubre nunca `Option<C>` y la resolución no es ambigua.
pub trait IntoColor {
/// Devuelve el nombre del color ya resuelto, o `None` si no se aplica ninguno.
fn into_color(self) -> Option<&'static str>;
}
impl<C: ColorName> IntoColor for C {
fn into_color(self) -> Option<&'static str> {
Some(self.name())
}
}
impl<C: ColorName> IntoColor for Option<C> {
fn into_color(self) -> Option<&'static str> {
self.map(ColorName::name)
}
}
// **< CoreColors >********************************************************************************* // **< CoreColors >*********************************************************************************
/// Paleta de colores predeterminada de PageTop. /// Paleta de colores predeterminada de PageTop.

View file

@ -19,7 +19,7 @@ use crate::web::http::StatusCode;
/// error. El contenido de cada región depende del [`Context`](crate::core::component::Context) y de /// error. El contenido de cada región depende del [`Context`](crate::core::component::Context) y de
/// su nombre lógico. /// su nombre lógico.
/// ///
/// Todos los métodos de este *trait* tienen una implementación por defecto, por lo que pueden /// Todos los métodos de este trait tienen una implementación por defecto, por lo que pueden
/// sobrescribirse selectivamente para crear nuevos temas con comportamientos distintos a los /// sobrescribirse selectivamente para crear nuevos temas con comportamientos distintos a los
/// predeterminados. /// predeterminados.
/// ///

View file

@ -81,9 +81,9 @@ pub enum PropsError {
/// ///
/// Las variantes `*Extra` permiten añadir valores tipados usando una clave. Están pensadas para /// Las variantes `*Extra` permiten añadir valores tipados usando una clave. Están pensadas para
/// ampliar el comportamiento de componentes ya existentes. Como no es posible añadir campos a la /// ampliar el comportamiento de componentes ya existentes. Como no es posible añadir campos a la
/// estructura de un componente ya definido, temas y extensiones pueden definir un *trait* con /// estructura de un componente ya definido, temas y extensiones pueden definir un trait con nuevos
/// nuevos métodos que leen y escriben valores extra en [`Props`]. Esos valores se interpretan como /// métodos que leen y escriben valores extra en [`Props`]. Esos valores se interpretan como si
/// si fueran valores internos del componente para tomar decisiones durante el renderizado. /// fueran valores internos del componente para tomar decisiones durante el renderizado.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum PropsOp { pub enum PropsOp {
/// Establece el identificador del componente normalizando el valor: recorta espacios, convierte /// Establece el identificador del componente normalizando el valor: recorta espacios, convierte
@ -374,7 +374,7 @@ impl PropsOp {
/// Las variantes [`SetExtra`](PropsOp::SetExtra) y [`RemoveExtra`](PropsOp::RemoveExtra), usando /// Las variantes [`SetExtra`](PropsOp::SetExtra) y [`RemoveExtra`](PropsOp::RemoveExtra), usando
/// [`PropsOp::set_extra()`] y [`PropsOp::remove_extra()`] respectivamente, permiten adjuntar /// [`PropsOp::set_extra()`] y [`PropsOp::remove_extra()`] respectivamente, permiten adjuntar
/// valores tipados a un `Props`. Son útiles para que temas y extensiones amplíen el comportamiento /// valores tipados a un `Props`. Son útiles para que temas y extensiones amplíen el comportamiento
/// de componentes ya existentes mediante *traits* con nuevos métodos que lean y escriban esos /// de componentes ya existentes mediante traits con nuevos métodos que lean y escriban esos
/// valores. /// valores.
/// ///
/// ```rust /// ```rust

View file

@ -125,8 +125,8 @@ use std::ops::Deref;
/// referencia a la versión del *crate* que lo usa. /// referencia a la versión del *crate* que lo usa.
pub const PAGETOP_VERSION: &str = env!("CARGO_PKG_VERSION"); pub const PAGETOP_VERSION: &str = env!("CARGO_PKG_VERSION");
/// Re-exporta el atributo [`async_trait`](https://docs.rs/async-trait) para implementar *traits* /// Re-exporta el atributo [`async_trait`](https://docs.rs/async-trait) para implementar traits con
/// con métodos `async`. /// métodos `async`.
/// ///
/// Si en el ámbito se declara `use pagetop::prelude::*` o `use pagetop::async_trait;` basta con /// Si en el ámbito se declara `use pagetop::prelude::*` o `use pagetop::async_trait;` basta con
/// usar el nombre corto `#[async_trait]`. Otra opción es usar su forma cualificada /// usar el nombre corto `#[async_trait]`. Otra opción es usar su forma cualificada

View file

@ -15,7 +15,7 @@ static FALLBACK_LANGID: LazyLock<LanguageIdentifier> = LazyLock::new(|| langid!(
/// Representa el identificador de idioma [`LanguageIdentifier`] asociado a un recurso. /// Representa el identificador de idioma [`LanguageIdentifier`] asociado a un recurso.
/// ///
/// Este *trait* permite que distintas estructuras expongan su idioma de forma uniforme. Las /// Este trait permite que distintas estructuras expongan su idioma de forma uniforme. Las
/// implementaciones deben garantizar que siempre se devuelve un identificador de idioma válido. Si /// implementaciones deben garantizar que siempre se devuelve un identificador de idioma válido. Si
/// el recurso no tiene uno asignado, se puede devolver, si procede, el identificador de idioma por /// el recurso no tiene uno asignado, se puede devolver, si procede, el identificador de idioma por
/// defecto de la aplicación ([`Locale::default_langid()`]). /// defecto de la aplicación ([`Locale::default_langid()`]).

View file

@ -10,7 +10,7 @@ use super::{LangId, LanguageIdentifier, Locale};
/// `?lang=...`. El comportamiento concreto depende de la política global [`LangNegotiation`] /// `?lang=...`. El comportamiento concreto depende de la política global [`LangNegotiation`]
/// configurada en la aplicación. /// configurada en la aplicación.
/// ///
/// El idioma resultante se expone a través del *trait* [`LangId`], de modo que pueda usarse /// El idioma resultante se expone a través del trait [`LangId`], de modo que pueda usarse
/// [`RequestLocale`] como cualquier otra fuente de idioma en PageTop. /// [`RequestLocale`] como cualquier otra fuente de idioma en PageTop.
/// ///
/// [`LangNegotiation`]: crate::global::LangNegotiation /// [`LangNegotiation`]: crate::global::LangNegotiation

97
tests/component_badge.rs Normal file
View file

@ -0,0 +1,97 @@
use pagetop::prelude::*;
#[pagetop::test]
async fn label_is_rendered_when_set() {
let mut badge = Badge::labeled(Lc::n("Admin"));
let html = badge.render(&mut Context::default()).await.into_string();
assert!(html.contains("Admin"));
}
#[pagetop::test]
async fn label_can_be_cleared_with_lc_none() {
let mut badge = Badge::labeled(Lc::n("Admin")).with_label(Lc::none());
let html = badge.render(&mut Context::default()).await.into_string();
assert!(!html.contains("Admin"));
// The badge itself must still render.
assert!(html.contains("<span"));
}
#[pagetop::test]
async fn renders_as_a_span_element() {
let mut badge = Badge::labeled(Lc::n("Admin"));
let html = badge.render(&mut Context::default()).await.into_string();
assert!(html.starts_with("<span"));
assert!(html.ends_with("</span>"));
}
#[pagetop::test]
async fn default_kind_is_secondary() {
let mut badge = Badge::labeled(Lc::n("Admin"));
let html = badge.render(&mut Context::default()).await.into_string();
assert!(html.contains(r#"class="badge badge-secondary""#));
}
#[pagetop::test]
async fn each_direct_constructor_sets_its_kind() {
let cases = [
(Badge::primary(Lc::n("x")), "badge-primary"),
(Badge::secondary(Lc::n("x")), "badge-secondary"),
(Badge::success(Lc::n("x")), "badge-success"),
(Badge::info(Lc::n("x")), "badge-info"),
(Badge::warning(Lc::n("x")), "badge-warning"),
(Badge::danger(Lc::n("x")), "badge-danger"),
];
for (mut badge, expected_class) in cases {
let html = badge.render(&mut Context::default()).await.into_string();
assert!(
html.contains(expected_class),
"expected `{expected_class}` in `{html}`"
);
}
}
#[pagetop::test]
async fn with_kind_overrides_the_default() {
let mut badge = Badge::labeled(Lc::n("Admin")).with_kind(BadgeKind::Danger);
let html = badge.render(&mut Context::default()).await.into_string();
assert!(html.contains(r#"class="badge badge-danger""#));
}
#[pagetop::test]
async fn direct_constructor_is_equivalent_to_labeled_with_kind() {
let mut from_constructor = Badge::danger(Lc::n("Admin"));
let mut from_builder = Badge::labeled(Lc::n("Admin")).with_kind(BadgeKind::Danger);
assert_eq!(
from_constructor
.render(&mut Context::default())
.await
.into_string(),
from_builder
.render(&mut Context::default())
.await
.into_string(),
);
}
#[pagetop::test]
async fn with_id_sets_the_identifier() {
let mut badge = Badge::labeled(Lc::n("Admin")).with_id("my-badge");
let html = badge.render(&mut Context::default()).await.into_string();
assert!(html.contains(r#"id="my-badge""#));
}
#[pagetop::test]
async fn with_prop_adds_extra_classes_alongside_the_kind_class() {
let mut badge = Badge::danger(Lc::n("Admin")).with_prop(PropsOp::add_classes("custom"));
let html = badge.render(&mut Context::default()).await.into_string();
assert!(html.contains("badge-danger"));
assert!(html.contains("custom"));
}

View file

@ -37,9 +37,10 @@ impl TestComp {
/// Creates a component with no id, with fixed output text. /// Creates a component with no id, with fixed output text.
fn text(text: &str) -> Self { fn text(text: &str) -> Self {
let mut c = Self::default(); Self {
c.text = text.to_string(); text: text.to_string(),
c ..Default::default()
}
} }
} }

View file

@ -31,7 +31,7 @@ impl Theme for MarkerTheme {
component: &mut dyn Component, component: &mut dyn Component,
_cx: &mut Context, _cx: &mut Context,
) -> Option<Result<Markup, ComponentError>> { ) -> Option<Result<Markup, ComponentError>> {
let template = (&*component).downcast_ref::<layout::Template>()?; let template = (*component).downcast_ref::<layout::Template>()?;
template.template().downcast_ref::<CoreTemplates>()?; template.template().downcast_ref::<CoreTemplates>()?;
Some(Ok(html! { "marker-template-output" })) Some(Ok(html! { "marker-template-output" }))
} }

View file

@ -5,7 +5,7 @@ use pagetop::prelude::*;
#[pagetop::test] #[pagetop::test]
async fn set_and_read_extra() { async fn set_and_read_extra() {
let props = Props::default().with_prop(PropsOp::set_extra("ext.flag", true)); let props = Props::default().with_prop(PropsOp::set_extra("ext.flag", true));
assert_eq!(*props.extra::<bool>("ext.flag").unwrap(), true); assert!(*props.extra::<bool>("ext.flag").unwrap());
} }
#[pagetop::test] #[pagetop::test]
@ -52,13 +52,13 @@ async fn extra_type_mismatch() {
#[pagetop::test] #[pagetop::test]
async fn extra_or_returns_value_when_found() { async fn extra_or_returns_value_when_found() {
let props = Props::default().with_prop(PropsOp::set_extra("ext.flag", true)); let props = Props::default().with_prop(PropsOp::set_extra("ext.flag", true));
assert_eq!(props.extra_or("ext.flag", false), true); assert!(props.extra_or("ext.flag", false));
} }
#[pagetop::test] #[pagetop::test]
async fn extra_or_returns_default_on_missing() { async fn extra_or_returns_default_on_missing() {
let props = Props::default(); let props = Props::default();
assert_eq!(props.extra_or("ext.flag", false), false); assert!(!props.extra_or("ext.flag", false));
} }
#[pagetop::test] #[pagetop::test]
@ -72,7 +72,7 @@ async fn extra_or_returns_default_on_type_mismatch() {
#[pagetop::test] #[pagetop::test]
async fn extra_or_default_returns_type_default_on_missing() { async fn extra_or_default_returns_type_default_on_missing() {
let props = Props::default(); let props = Props::default();
assert_eq!(props.extra_or_default::<bool>("ext.flag"), false); assert!(!props.extra_or_default::<bool>("ext.flag"));
assert_eq!(props.extra_or_default::<i32>("ext.count"), 0); assert_eq!(props.extra_or_default::<i32>("ext.count"), 0);
} }
@ -93,7 +93,7 @@ async fn extra_or_else_calls_closure_on_missing() {
async fn clone_preserves_extras() { async fn clone_preserves_extras() {
let props = Props::default().with_prop(PropsOp::set_extra("ext.flag", true)); let props = Props::default().with_prop(PropsOp::set_extra("ext.flag", true));
let cloned = props.clone(); let cloned = props.clone();
assert_eq!(*cloned.extra::<bool>("ext.flag").unwrap(), true); assert!(*cloned.extra::<bool>("ext.flag").unwrap());
} }
#[pagetop::test] #[pagetop::test]
@ -103,7 +103,7 @@ async fn multiple_extras_with_different_types() {
.with_prop(PropsOp::set_extra("ext.count", 42_u32)) .with_prop(PropsOp::set_extra("ext.count", 42_u32))
.with_prop(PropsOp::set_extra("ext.label", "hello".to_string())); .with_prop(PropsOp::set_extra("ext.label", "hello".to_string()));
assert_eq!(*props.extra::<bool>("ext.flag").unwrap(), true); assert!(*props.extra::<bool>("ext.flag").unwrap());
assert_eq!(*props.extra::<u32>("ext.count").unwrap(), 42); assert_eq!(*props.extra::<u32>("ext.count").unwrap(), 42);
assert_eq!(props.extra::<String>("ext.label").unwrap(), "hello"); assert_eq!(props.extra::<String>("ext.label").unwrap(), "hello");
} }