#[cfg(test)]
mod tests {
use mirui::ecs::World;
use mirui::ui;
use mirui::ui::IdMap;
use mirui::ui::builder::WidgetBuilder;
use mirui::ui::widgets::{Button, Checkbox};
#[test]
fn capital_name_inserts_component() {
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
Button (
normal_color: mirui::ui::ColorToken::Surface,
) {}
};
let entities = world.query::<Button>().collect();
assert_eq!(entities.len(), 1, "exactly one Button entity");
let btn = world.get::<Button>(entities[0]).unwrap();
assert!(matches!(
btn.normal_color,
mirui::ui::ThemedColor::Token(mirui::ui::ColorToken::Surface)
));
}
#[test]
fn button_accepts_positional_and_named_labels() {
use mirui::ui::widgets::Text;
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
Row () {
Button("Save") {}
Button (text: "Cancel") {}
}
};
let entities = world.query::<Text>().collect();
let labels: Vec<_> = entities
.into_iter()
.map(|entity| world.get::<Text>(entity).unwrap().resolve(&world))
.collect();
assert_eq!(labels, ["Save", "Cancel"]);
}
#[test]
fn capital_name_uses_default_for_omitted_fields() {
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
Checkbox () {}
};
let entities = world.query::<Checkbox>().collect();
assert_eq!(entities.len(), 1);
let cb = world.get::<Checkbox>(entities[0]).unwrap();
assert_eq!(cb.checked, false);
}
#[test]
fn text_widget_uses_tuple_init() {
use mirui::ui::widgets::Text;
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
Text (text: "Hello") {}
};
let entities = world.query::<Text>().collect();
assert_eq!(entities.len(), 1);
let text = world.get::<Text>(entities[0]).unwrap();
assert_eq!(text.resolve(&world), "Hello");
}
#[test]
fn text_widget_routes_font_size_to_style() {
use mirui::ui::Style;
use mirui::ui::widgets::Text;
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
Text (
"Hello",
width: mirui::types::Dimension::Content,
height: mirui::types::Dimension::Auto,
font_size: 18
) {}
};
let entity = world.query::<Text>().collect()[0];
let style = world.get::<Style>(entity).unwrap();
assert_eq!(style.font_size, Some(18));
assert_eq!(style.layout.width, mirui::types::Dimension::Content);
assert_eq!(style.layout.height, mirui::types::Dimension::Auto);
}
#[test]
fn text_widget_routes_font_stack_and_paragraph() {
use mirui::render::font::{FontStack, FontToken};
use mirui::ui::Style;
use mirui::ui::widgets::{ParagraphStyle, Text, TextDirection, TextWrap};
const PRIMARY: FontToken = FontToken::Custom("primary");
static FALLBACKS: [FontToken; 2] = [
FontToken::Custom("fallback-cjk"),
FontToken::Custom("fallback-arabic"),
];
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
let paragraph = ParagraphStyle {
wrap: TextWrap::NoWrap,
direction: TextDirection::RightToLeft,
max_lines: Some(1),
..ParagraphStyle::default()
};
let expected_paragraph = paragraph.clone();
ui! {
:(
parent: root
world: &mut world
:)
Text (
"Ù…Ø±ØØ¨Ø§",
font_stack: FontStack::new(PRIMARY).with_fallbacks(&FALLBACKS[..]),
paragraph: paragraph
) {}
};
let entity = world.query::<Text>().collect()[0];
let style = world.get::<Style>(entity).unwrap();
let text = world.get::<Text>(entity).unwrap();
assert_eq!(style.font_stack.primary(), &PRIMARY);
assert_eq!(style.font_stack.fallbacks(), &FALLBACKS);
assert_eq!(text.paragraph(), &expected_paragraph);
}
#[test]
fn text_widget_reacts_to_paragraph_signal() {
use mirui::core::reactive::{Signal, flush_signal_dirty};
use mirui::ui::widgets::{ParagraphStyle, Text, TextAlign, TextOverflow, TextWrap};
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
let paragraph = Signal::new(ParagraphStyle::default());
let paragraph_view = paragraph.clone();
ui! {
:(
parent: root
world: &mut world
:)
Text (
"responsive text",
paragraph: ${ paragraph_view.get() }
) {}
};
let entity = world.query::<Text>().collect()[0];
paragraph.set(ParagraphStyle {
wrap: TextWrap::Grapheme,
align: TextAlign::End,
overflow: TextOverflow::Ellipsis,
max_lines: Some(2),
..ParagraphStyle::default()
});
flush_signal_dirty(&mut world);
let actual = world.get::<Text>(entity).unwrap().paragraph();
assert_eq!(actual.wrap, TextWrap::Grapheme);
assert_eq!(actual.align, TextAlign::End);
assert_eq!(actual.overflow, TextOverflow::Ellipsis);
assert_eq!(actual.max_lines, Some(2));
}
#[test]
fn text_input_text_color_routes_to_field() {
use mirui::ui::widgets::TextInput;
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
TextInput (
text_color: mirui::ui::ColorToken::Primary,
) {}
};
let entities = world.query::<TextInput>().collect();
assert_eq!(entities.len(), 1);
let ti = world.get::<TextInput>(entities[0]).unwrap();
assert!(matches!(
ti.text_color,
mirui::ui::ThemedColor::Token(mirui::ui::ColorToken::Primary)
));
}
#[test]
fn text_widget_supports_positional_arg() {
use mirui::ui::widgets::Text;
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
Text("Positional") {}
};
let entities = world.query::<Text>().collect();
assert_eq!(entities.len(), 1);
let text = world.get::<Text>(entities[0]).unwrap();
assert_eq!(text.resolve(&world), "Positional");
}
#[test]
fn image_accepts_a_borrowed_vector_path_positionally() {
use mirui::render::path::{Path, PathCmd};
use mirui::types::Point;
use mirui::ui::widgets::{Image, ImageSource};
static ICON: &[PathCmd] = &[
PathCmd::MoveTo(Point::ZERO),
PathCmd::LineTo(Point {
x: mirui::types::Fixed::from_int(24),
y: mirui::types::Fixed::from_int(24),
}),
];
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
let icon = Path::from_static(ICON);
ui! {
:(
parent: root
world: &mut world
:)
Image (icon) {}
};
let entity = world.query::<Image>().collect()[0];
assert!(matches!(
&world.get::<Image>(entity).unwrap().src,
ImageSource::Vector(path) if path.commands().as_ptr() == ICON.as_ptr()
));
}
#[test]
fn typed_icon_assets_feed_image_and_icon_widgets() {
use mirui::ui::icons::ICON_HOME;
use mirui::ui::widgets::{Icon, Image, ImageSource};
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
Column {
Image (ICON_HOME) {}
Icon (path: ICON_HOME) {}
}
};
let image_entity = world.query::<Image>().collect()[0];
let icon_entity = world.query::<Icon>().collect()[0];
let ImageSource::Vector(image_path) = &world.get::<Image>(image_entity).unwrap().src else {
panic!("icon image source");
};
assert_eq!(
image_path.commands().as_ptr(),
ICON_HOME.path().commands().as_ptr()
);
assert_eq!(
world
.get::<Icon>(icon_entity)
.unwrap()
.path
.commands()
.as_ptr(),
ICON_HOME.path().commands().as_ptr()
);
}
#[test]
fn row_widget_implies_row_direction() {
use mirui::ui::Style;
use mirui::ui::layout::FlexDirection;
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
Row (width: 100) {}
};
let entities = world.query::<Style>().collect();
let row_count = entities
.iter()
.filter(|&&e| {
world
.get::<Style>(e)
.map(|s| matches!(s.layout.direction, FlexDirection::Row))
.unwrap_or(false)
})
.count();
assert!(
row_count >= 1,
"Row widget must default to FlexDirection::Row"
);
}
#[test]
fn column_widget_implies_column_direction() {
use mirui::ui::Style;
use mirui::ui::layout::FlexDirection;
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
Column (width: 100) {}
};
let entities = world.query::<Style>().collect();
let col_count = entities
.iter()
.filter(|&&e| {
world
.get::<Style>(e)
.map(|s| matches!(s.layout.direction, FlexDirection::Column))
.unwrap_or(false)
})
.count();
assert!(
col_count >= 1,
"Column widget must default to FlexDirection::Column"
);
}
#[test]
fn gap_shorthand_routes_to_both_axes() {
use mirui::types::Dimension;
use mirui::ui::Style;
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
Column (gap: 12) {}
};
let entity = world
.query::<Style>()
.collect()
.into_iter()
.find(|entity| *entity != root)
.unwrap();
let layout = world.get::<Style>(entity).unwrap().layout;
assert_eq!(layout.row_gap, Dimension::px(12));
assert_eq!(layout.column_gap, Dimension::px(12));
}
#[test]
fn size_constraints_route_to_layout() {
use mirui::types::Dimension;
use mirui::ui::Style;
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
View (
min_width: 120,
max_width: 320,
min_height: 80,
max_height: 240
) {}
};
let entity = world
.query::<Style>()
.collect()
.into_iter()
.find(|entity| *entity != root)
.unwrap();
let layout = world.get::<Style>(entity).unwrap().layout;
assert_eq!(layout.min_width, Dimension::px(120));
assert_eq!(layout.max_width, Dimension::px(320));
assert_eq!(layout.min_height, Dimension::px(80));
assert_eq!(layout.max_height, Dimension::px(240));
}
#[test]
fn shrink_routes_to_layout() {
use mirui::types::Fixed;
use mirui::ui::Style;
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
View (shrink: 0.5) {}
};
let entity = world
.query::<Style>()
.collect()
.into_iter()
.find(|entity| *entity != root)
.unwrap();
let layout = world.get::<Style>(entity).unwrap().layout;
assert_eq!(layout.shrink, Fixed::HALF);
}
#[test]
fn flex_wrap_routes_to_layout() {
use mirui::ui::Style;
use mirui::ui::layout::FlexWrap;
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
Row (wrap: FlexWrap::Wrap) {}
};
let entity = world
.query::<Style>()
.collect()
.into_iter()
.find(|entity| *entity != root)
.unwrap();
let layout = world.get::<Style>(entity).unwrap().layout;
assert_eq!(layout.wrap, FlexWrap::Wrap);
}
#[test]
fn lowercase_name_stays_layout_fallback() {
let mut world = World::new();
world.insert_resource(IdMap::new());
let root = WidgetBuilder::new(&mut world).id();
ui! {
:(
parent: root
world: &mut world
:)
row (
direction: mirui::ui::layout::FlexDirection::Row,
width: 100
) {}
};
let buttons = world.query::<Button>().collect();
assert!(
buttons.is_empty(),
"lowercase name should not implicitly insert any component"
);
}
}