use super::*;
use crate::core::Rect;
use crate::widget::view_widgets::virtual_list::VirtualList;
#[test]
fn default_factory_registers_core_capabilities() {
let factory = WidgetFactory::new_with_defaults();
assert!(factory.capability("label").is_some());
assert!(factory.capability("checkbox").is_some());
assert!(factory.capability("radiobutton").is_some());
assert!(factory.capability("slider").is_some());
assert!(factory.capability("lineedit").is_some());
assert!(factory.capability("list_view").is_some());
assert!(factory.capability("treeview").is_some());
assert!(factory.capability("table").is_some());
assert!(factory.capability("dataview").is_some());
assert!(factory.capability("menu").is_some());
assert!(factory.capability("menubar").is_some());
assert!(factory.capability("toolbar").is_some());
assert!(factory.capability("ribbon").is_some());
assert!(factory.capability("colorpicker").is_some());
assert!(factory.capability("code_editor").is_some());
assert!(factory.capability("gantt").is_some());
assert!(factory.capability("terminalview").is_some());
assert!(factory.capability("snackbar").is_some());
assert!(factory.capability("mapview").is_some());
assert!(factory.capability("mediaplayer").is_some());
assert!(factory.capability("breadcrumb").is_some());
assert!(factory.capability("splitbutton").is_some());
assert!(factory.capability("segmentedcontrol").is_some());
assert!(factory.capability("chips").is_some());
assert!(factory.capability("gridwidget").is_some());
assert!(factory.capability("freeformshape").is_some());
assert!(factory.capability("progressbar").is_some());
assert!(factory.capability("scrollbar").is_some());
assert!(factory.capability("listbox").is_some());
assert!(factory.capability("spinbox").is_some());
assert!(factory.capability("combobox").is_some());
assert!(factory.capability("dial").is_some());
assert!(factory.capability("window").is_some());
assert!(factory.capability("groupbox").is_some());
assert!(factory.capability("splitter").is_some());
assert!(factory.capability("lcdnumber").is_some());
assert!(factory.capability("commandlink").is_some());
assert!(factory.capability("fontcombobox").is_some());
assert!(factory.capability("action").is_some());
assert!(factory.capability("toolbox").is_some());
assert!(factory.capability("tabbar").is_some());
assert!(factory.capability("calendar").is_some());
assert!(factory.capability("dateedit").is_some());
assert!(factory.capability("timeedit").is_some());
assert!(factory.capability("datagrid").is_some());
assert!(factory.capability("treetable").is_some());
assert!(factory.capability("virtualtable").is_some());
assert!(factory.capability("messagebox").is_some());
assert!(factory.capability("filedialog").is_some());
assert!(factory.capability("fontdialog").is_some());
assert!(factory.capability("inputdialog").is_some());
assert!(factory.capability("progressdialog").is_some());
assert!(factory.capability("popupwindow").is_some());
assert!(factory.capability("scrollarea").is_some());
assert!(factory.capability("tabwidget").is_some());
assert!(factory.capability("stackedwidget").is_some());
assert!(factory.capability("collapsiblepane").is_some());
assert!(factory.capability("dockwidget").is_some());
assert!(factory.capability("mdiarea").is_some());
assert!(factory.capability("textedit").is_some());
assert!(factory.capability("webview").is_some());
assert!(factory.capability("piemenu").is_some());
assert!(factory.capability("datetimepicker").is_some());
}
#[test]
fn factory_creates_registered_widgets_by_alias() {
let factory = WidgetFactory::new_with_defaults();
let rect = Rect::new(1, 2, 120, 40);
let button = factory.create("btn", rect, "Run").expect("button must be created via alias");
assert_eq!(button.kind(), WidgetKind::Button);
assert_eq!(button.geometry(), rect);
let label =
factory.create("label", rect, "Name").expect("label must be created by canonical name");
assert_eq!(label.kind(), WidgetKind::Label);
let check_box =
factory.create("checkbox", rect, "Accept").expect("checkbox must be created via alias");
assert_eq!(check_box.kind(), WidgetKind::CheckBox);
let radio_button = factory
.create("radiobutton", rect, "Option")
.expect("radio button must be created via alias");
assert_eq!(radio_button.kind(), WidgetKind::RadioButton);
let slider =
factory.create("slider", rect, "").expect("slider must be created by canonical name");
assert_eq!(slider.kind(), WidgetKind::Slider);
let line_edit =
factory.create("input", rect, "hello").expect("line edit must be created via alias");
assert_eq!(line_edit.kind(), WidgetKind::LineEdit);
let table = factory.create("table", rect, "").expect("table widget must be created via alias");
assert_eq!(table.kind(), WidgetKind::Table);
let data_view =
factory.create("dataview", rect, "").expect("data view must be created via alias");
assert_eq!(data_view.kind(), WidgetKind::DataView);
let tree = factory.create("treeview", rect, "").expect("tree view must be created via alias");
assert_eq!(tree.kind(), WidgetKind::TreeView);
let ribbon = factory.create("ribbon", rect, "").expect("ribbon bar must be created via alias");
assert_eq!(ribbon.kind(), WidgetKind::RibbonBar);
let color_picker =
factory.create("colorpicker", rect, "").expect("color picker must be created via alias");
assert_eq!(color_picker.kind(), WidgetKind::ColorPicker);
let color_dialog =
factory.create("color_dialog", rect, "").expect("colour dialog must be registered");
assert_eq!(color_dialog.kind(), WidgetKind::ColorDialog);
let code_editor = factory
.create("codeeditor", rect, "// code")
.expect("code editor must be created via alias");
assert_eq!(code_editor.kind(), WidgetKind::RichEdit);
let gantt = factory.create("gantt", rect, "").expect("gantt must be created via alias");
assert_eq!(gantt.kind(), WidgetKind::Chart);
let terminal = factory
.create("terminalview", rect, "echo hi")
.expect("terminal view must be created via alias");
assert_eq!(terminal.kind(), WidgetKind::TextEdit);
let snackbar =
factory.create("snackbar", rect, "Saved").expect("snackbar must be created via alias");
assert_eq!(snackbar.kind(), WidgetKind::StatusBar);
let map = factory.create("mapview", rect, "").expect("map view must be created via alias");
assert_eq!(map.kind(), WidgetKind::Canvas);
let media =
factory.create("mediaplayer", rect, "").expect("media player must be created via alias");
assert_eq!(media.kind(), WidgetKind::WebEngineView);
let breadcrumb =
factory.create("breadcrumb", rect, "").expect("breadcrumb must be created via alias");
assert_eq!(breadcrumb.kind(), WidgetKind::Breadcrumb);
let split_button = factory
.create("splitbutton", rect, "Menu")
.expect("split button must be created via alias");
assert_eq!(split_button.kind(), WidgetKind::ToolButton);
let segmented = factory
.create("segmentedcontrol", rect, "")
.expect("segmented control must be created via alias");
assert_eq!(segmented.kind(), WidgetKind::ToggleButton);
let chips = factory.create("chips", rect, "").expect("chip must be created via alias");
assert_eq!(chips.kind(), WidgetKind::Chip);
let grid =
factory.create("gridwidget", rect, "").expect("grid widget must be created via alias");
assert_eq!(grid.kind(), WidgetKind::Grid);
let shape = factory
.create("freeformshape", rect, "")
.expect("freeform shape must be created via alias");
assert_eq!(shape.kind(), WidgetKind::FreeformShape);
let msg =
factory.create("messagebox", rect, "").expect("message box must be created via alias");
assert_eq!(msg.kind(), WidgetKind::MessageBox);
let fd = factory.create("filedialog", rect, "").expect("file dialog must be created via alias");
assert_eq!(fd.kind(), WidgetKind::FileDialog);
let font_dialog =
factory.create("fontdialog", rect, "").expect("font dialog must be created via alias");
assert_eq!(font_dialog.kind(), WidgetKind::FontDialog);
let input_dialog =
factory.create("inputdialog", rect, "").expect("input dialog must be created via alias");
assert_eq!(input_dialog.kind(), WidgetKind::InputDialog);
let progress_dialog = factory
.create("progressdialog", rect, "")
.expect("progress dialog must be created via alias");
assert_eq!(progress_dialog.kind(), WidgetKind::ProgressDialog);
let popup =
factory.create("popupwindow", rect, "").expect("popup window must be created via alias");
assert_eq!(popup.kind(), WidgetKind::PopupWindow);
let scroll =
factory.create("scrollarea", rect, "").expect("scroll area must be created via alias");
assert_eq!(scroll.kind(), WidgetKind::ScrollArea);
let tab_widget =
factory.create("tabwidget", rect, "").expect("tab widget must be created via alias");
assert_eq!(tab_widget.kind(), WidgetKind::TabWidget);
let stacked = factory
.create("stackedwidget", rect, "")
.expect("stacked widget must be created via alias");
assert_eq!(stacked.kind(), WidgetKind::StackedWidget);
let collapsible = factory
.create("collapsiblepane", rect, "")
.expect("collapsible pane must be created via alias");
assert_eq!(collapsible.kind(), WidgetKind::CollapsiblePane);
let dock =
factory.create("dockwidget", rect, "").expect("dock widget must be created via alias");
assert_eq!(dock.kind(), WidgetKind::DockWidget);
let mdi = factory.create("mdiarea", rect, "").expect("mdi area must be created via alias");
assert_eq!(mdi.kind(), WidgetKind::MdiArea);
let text_edit =
factory.create("textedit", rect, "").expect("text edit must be created via alias");
assert_eq!(text_edit.kind(), WidgetKind::TextEdit);
let web = factory.create("webview", rect, "").expect("web view must be created via alias");
assert_eq!(web.kind(), WidgetKind::WebEngineView);
let pie_menu = factory.create("piemenu", rect, "").expect("pie menu must be created via alias");
assert_eq!(pie_menu.kind(), WidgetKind::PieMenu);
let datetime = factory
.create("datetimepicker", rect, "")
.expect("date time picker must be created via alias");
assert_eq!(datetime.kind(), WidgetKind::DateTimePicker);
}
#[test]
fn new_widget_kinds_resolve_their_own_kind() {
let factory = WidgetFactory::new_with_defaults();
let rect = Rect::new(0, 0, 200, 120);
let tree_table = factory.create("tree_table", rect, "").expect("tree table must be created");
assert_eq!(tree_table.kind(), WidgetKind::TreeTable);
let breadcrumb = factory.create("breadcrumb", rect, "").expect("breadcrumb must be created");
assert_eq!(breadcrumb.kind(), WidgetKind::Breadcrumb);
let pad = factory.create("signature_pad", rect, "").expect("signature pad must be created");
assert_eq!(pad.kind(), WidgetKind::SignaturePad);
let zone = factory.create("drop_zone", rect, "").expect("drop zone must be created");
assert_eq!(zone.kind(), WidgetKind::DropZone);
let dialog = factory.create("dialog", rect, "").expect("dialog must be created");
assert_eq!(dialog.kind(), WidgetKind::Dialog);
}
#[test]
fn capability_by_kind_returns_expected_schema() {
let factory = WidgetFactory::new_with_defaults();
let capability = factory.capability_by_kind(WidgetKind::Button).unwrap();
assert_eq!(capability.canonical_name, "button");
assert!(capability.properties.iter().any(|p| p.name == "text"));
let capability = factory.capability_by_kind(WidgetKind::Slider).unwrap();
assert_eq!(capability.canonical_name, "slider");
assert!(capability.properties.iter().any(|p| p.name == "value"));
}
#[test]
fn create_unknown_widget_returns_none() {
let factory = WidgetFactory::new_with_defaults();
assert!(factory.create("not_registered", Rect::new(0, 0, 1, 1), "").is_none());
}
#[test]
fn create_by_kind_uses_registered_constructor() {
let factory = WidgetFactory::new_with_defaults();
let rect = Rect::new(10, 20, 180, 30);
let widget = factory.create_by_kind(WidgetKind::Button, rect, "Click").unwrap();
assert_eq!(widget.kind(), WidgetKind::Button);
assert_eq!(widget.geometry(), rect);
}
#[test]
fn read_property_returns_value_for_registered_widget() {
let factory = WidgetFactory::new_with_defaults();
let widget = factory.create("btn", Rect::new(0, 0, 100, 30), "Save").unwrap();
let text = factory.read_property(widget.as_ref(), "text").unwrap();
assert_eq!(text, CapabilityValue::String("Save".to_string()));
}
#[test]
fn read_property_returns_unknown_property_for_missing_schema_item() {
let factory = WidgetFactory::new_with_defaults();
let widget = factory.create("btn", Rect::new(0, 0, 100, 30), "Save").unwrap();
let result = factory.read_property(widget.as_ref(), "nonexistent");
assert_eq!(result, Err(CapabilityAccessError::UnknownProperty));
}
#[test]
fn read_property_is_case_and_separator_insensitive() {
let factory = WidgetFactory::new_with_defaults();
let widget = factory.create("btn", Rect::new(0, 0, 100, 30), "Important").unwrap();
let text = factory.read_property(widget.as_ref(), "TEXT").unwrap();
assert_eq!(text, CapabilityValue::String("Important".to_string()));
let text2 = factory.read_property(widget.as_ref(), "tool-tip").unwrap();
assert_eq!(text2, CapabilityValue::String("".to_string()));
let text3 = factory.read_property(widget.as_ref(), "t o o l t i p").unwrap();
assert_eq!(text3, CapabilityValue::String("".to_string()));
}
#[test]
fn write_property_updates_mutable_scalar_fields() {
let factory = WidgetFactory::new_with_defaults();
let mut widget = factory.create("btn", Rect::new(0, 0, 100, 30), "").unwrap();
factory
.write_property(widget.as_mut(), "text", CapabilityValue::String("Updated".to_string()))
.unwrap();
let text = factory.read_property(widget.as_ref(), "text").unwrap();
assert_eq!(text, CapabilityValue::String("Updated".to_string()));
factory.write_property(widget.as_mut(), "enabled", CapabilityValue::Bool(false)).unwrap();
let enabled = factory.read_property(widget.as_ref(), "enabled").unwrap();
assert_eq!(enabled, CapabilityValue::Bool(false));
}
#[test]
fn write_property_reports_readonly_property() {
let factory = WidgetFactory::new_with_defaults();
let mut list_view = factory.create("listview", Rect::new(0, 0, 200, 60), "").unwrap();
let result =
factory.write_property(list_view.as_mut(), "has_model", CapabilityValue::Bool(false));
assert_eq!(result, Err(CapabilityAccessError::ReadOnlyProperty));
}
#[test]
fn write_property_reports_type_mismatch() {
let factory = WidgetFactory::new_with_defaults();
let mut widget = factory.create("btn", Rect::new(0, 0, 100, 30), "").unwrap();
let result = factory.write_property(widget.as_mut(), "text", CapabilityValue::Bool(true));
assert_eq!(result, Err(CapabilityAccessError::TypeMismatch));
}
#[test]
fn read_property_covers_declared_scalar_fields() {
let factory = WidgetFactory::new_with_defaults();
let mut slider = factory.create("slider", Rect::new(0, 0, 200, 30), "").unwrap();
assert_eq!(factory.read_property(slider.as_ref(), "minimum"), Ok(CapabilityValue::Int(0)));
assert_eq!(factory.read_property(slider.as_ref(), "maximum"), Ok(CapabilityValue::Int(100)));
factory.write_property(slider.as_mut(), "value", CapabilityValue::Int(42)).unwrap();
assert_eq!(factory.read_property(slider.as_ref(), "value"), Ok(CapabilityValue::Int(42)));
}
#[test]
fn read_property_returns_null_for_optional_projection_fields() {
let factory = WidgetFactory::new_with_defaults();
let list_view = factory.create("listview", Rect::new(0, 0, 200, 60), "").unwrap();
assert_eq!(factory.read_property(list_view.as_ref(), "focused_row"), Ok(CapabilityValue::Null));
}
#[test]
fn write_property_supports_enum_backed_fields() {
let factory = WidgetFactory::new_with_defaults();
let mut check_box = factory.create("checkbox", Rect::new(0, 0, 100, 30), "Option").unwrap();
factory
.write_property(check_box.as_mut(), "state", CapabilityValue::String("checked".to_string()))
.unwrap();
assert_eq!(
factory.read_property(check_box.as_ref(), "state"),
Ok(CapabilityValue::String("checked".to_string()))
);
factory
.write_property(
check_box.as_mut(),
"state",
CapabilityValue::String("unchecked".to_string()),
)
.unwrap();
assert_eq!(
factory.read_property(check_box.as_ref(), "state"),
Ok(CapabilityValue::String("unchecked".to_string()))
);
factory
.write_property(check_box.as_mut(), "state", CapabilityValue::String("partial".to_string()))
.unwrap();
assert_eq!(
factory.read_property(check_box.as_ref(), "state"),
Ok(CapabilityValue::String("partially_checked".to_string()))
);
}
#[test]
fn write_property_supports_r3_data_controls() {
let factory = WidgetFactory::new_with_defaults();
let mut list_view = factory.create("listview", Rect::new(0, 0, 200, 60), "").unwrap();
factory
.write_property(
list_view.as_mut(),
"view_mode",
CapabilityValue::String("icon".to_string()),
)
.unwrap();
assert_eq!(
factory.read_property(list_view.as_ref(), "view_mode"),
Ok(CapabilityValue::String("icon".to_string()))
);
factory
.write_property(
list_view.as_mut(),
"view_mode",
CapabilityValue::String("details".to_string()),
)
.unwrap();
assert_eq!(
factory.read_property(list_view.as_ref(), "view_mode"),
Ok(CapabilityValue::String("details".to_string()))
);
factory
.write_property(
list_view.as_mut(),
"selection_mode",
CapabilityValue::String("multi".to_string()),
)
.unwrap();
assert_eq!(
factory.read_property(list_view.as_ref(), "selection_mode"),
Ok(CapabilityValue::String("multi".to_string()))
);
let mut tree_view = factory.create("treeview", Rect::new(0, 0, 200, 60), "").unwrap();
factory.write_property(tree_view.as_mut(), "focused_node", CapabilityValue::Null).unwrap();
assert_eq!(
factory.read_property(tree_view.as_ref(), "focused_node"),
Ok(CapabilityValue::Null)
);
let mut data_grid = factory.create("datagrid", Rect::new(0, 0, 300, 100), "").unwrap();
factory.write_property(data_grid.as_mut(), "row_height", CapabilityValue::UInt(28)).unwrap();
assert_eq!(
factory.read_property(data_grid.as_ref(), "row_height"),
Ok(CapabilityValue::UInt(28))
);
let mut dg2 = factory.create("datagrid", Rect::new(0, 0, 300, 100), "").unwrap();
factory.write_property(dg2.as_mut(), "column_width", CapabilityValue::UInt(150)).unwrap();
assert_eq!(factory.read_property(dg2.as_ref(), "column_width"), Ok(CapabilityValue::UInt(150)));
}
#[test]
fn write_property_accepts_null_for_optional_focus_fields() {
let factory = WidgetFactory::new_with_defaults();
let mut tree_view = factory.create("treeview", Rect::new(0, 0, 200, 60), "").unwrap();
factory.write_property(tree_view.as_mut(), "focused_node", CapabilityValue::Null).unwrap();
assert_eq!(
factory.read_property(tree_view.as_ref(), "focused_node"),
Ok(CapabilityValue::Null)
);
}
#[cfg(not(alloc_frugal))]
#[test]
fn schema_defaults_are_readable_and_writable_when_declared() {
let factory = WidgetFactory::new_with_defaults();
for capability in factory.capabilities() {
for prop in capability.properties {
if prop.readable {
assert!(
factory.default_property_value(capability.canonical_name, prop.name).is_ok(),
"default_property_value should be readable for {}::{}",
capability.canonical_name,
prop.name
);
}
}
}
}
#[cfg(not(alloc_frugal))]
#[test]
fn default_property_value_returns_schema_defaults() {
let factory = WidgetFactory::new_with_defaults();
assert_eq!(
factory.default_property_value("button", "text"),
Ok(CapabilityValue::String(String::new()))
);
assert_eq!(
factory.default_property_value("button", "pressed"),
Ok(CapabilityValue::Bool(false))
);
assert_eq!(
factory.default_property_value("button", "default"),
Ok(CapabilityValue::Bool(false))
);
assert_eq!(
factory.default_property_value("button", "enabled"),
Ok(CapabilityValue::Bool(true))
);
assert_eq!(factory.default_property_value("slider", "minimum"), Ok(CapabilityValue::Int(0)));
assert_eq!(factory.default_property_value("slider", "maximum"), Ok(CapabilityValue::Int(100)));
assert_eq!(
factory.default_property_value("slider", "orientation"),
Ok(CapabilityValue::String("horizontal".to_string()))
);
assert_eq!(
factory.default_property_value("progress_bar", "progress"),
Ok(CapabilityValue::Float(0.0))
);
assert_eq!(
factory.default_property_value("progress_bar", "inverted_appearance"),
Ok(CapabilityValue::Bool(false))
);
assert_eq!(
factory.default_property_value("check_box", "state"),
Ok(CapabilityValue::String("unchecked".to_string()))
);
assert_eq!(
factory.default_property_value("check_box", "checked"),
Ok(CapabilityValue::Bool(false))
);
assert_eq!(
factory.default_property_value("listbox", "item_height"),
Ok(CapabilityValue::Float(20.0))
);
assert_eq!(
factory.default_property_value("spinbox", "wrapping"),
Ok(CapabilityValue::Bool(false))
);
assert_eq!(
factory.default_property_value("spinbox", "special_value_text"),
Ok(CapabilityValue::Null)
);
assert_eq!(
factory.default_property_value("combobox", "current_index"),
Ok(CapabilityValue::Null)
);
assert_eq!(
factory.default_property_value("combobox", "max_visible_items"),
Ok(CapabilityValue::UInt(10))
);
assert_eq!(
factory.default_property_value("dial", "notch_target"),
Ok(CapabilityValue::Float(3.7))
);
assert_eq!(
factory.default_property_value("lcdnumber", "num_digits"),
Ok(CapabilityValue::Int(6))
);
assert_eq!(
factory.default_property_value("lcdnumber", "segment_style"),
Ok(CapabilityValue::String("filled".to_string()))
);
assert_eq!(
factory.default_property_value("lcdnumber", "mode"),
Ok(CapabilityValue::String("dec".to_string()))
);
assert_eq!(
factory.default_property_value("fontcombobox", "current_font_family"),
Ok(CapabilityValue::String("Arial".to_string()))
);
assert_eq!(factory.default_property_value("action", "command_id"), Ok(CapabilityValue::Null));
assert_eq!(
factory.default_property_value("action", "separator"),
Ok(CapabilityValue::Bool(false))
);
assert_eq!(
factory.default_property_value("line_edit", "max_length"),
Ok(CapabilityValue::Null)
);
assert_eq!(
factory.default_property_value("listview", "selection_mode"),
Ok(CapabilityValue::String("single".to_string()))
);
assert_eq!(
factory.default_property_value("toolbar", "orientation"),
Ok(CapabilityValue::String("horizontal".to_string()))
);
assert_eq!(
factory.default_property_value("color_picker", "show_alpha"),
Ok(CapabilityValue::Bool(true))
);
assert_eq!(
factory.default_property_value("tabbar", "tab_min_width"),
Ok(CapabilityValue::UInt(40))
);
assert_eq!(
factory.default_property_value("calendar", "first_day_of_week"),
Ok(CapabilityValue::String("mon".to_string()))
);
assert_eq!(
factory.default_property_value("dateedit", "display_format"),
Ok(CapabilityValue::String("yyyy-MM-dd".to_string()))
);
assert_eq!(
factory.default_property_value("timeedit", "display_format"),
Ok(CapabilityValue::String("HH:mm:ss".to_string()))
);
}
#[test]
fn property_schema_lookup_is_normalized() {
let factory = WidgetFactory::new_with_defaults();
let schema = factory
.property_schema("line-edit", "max length")
.expect("normalized schema lookup should succeed");
assert_eq!(schema.name, "max_length");
assert_eq!(schema.value_kind, PropertyValueKind::UInt);
assert!(schema.readable);
assert!(schema.writable);
}
#[test]
fn every_kind_with_a_constructor_has_a_resolvable_factory_name() {
let factory = WidgetFactory::new_with_defaults();
for kind in [WidgetKind::Panel, WidgetKind::DockPanel] {
let name = crate::widget::capability::factory_name_for_kind(kind);
assert!(
!name.is_empty(),
"{kind:?} resolves to an empty factory name, so `theme::apply_active_theme` \
will silently skip it; add an alias row in `alias_factory_name`"
);
assert!(
factory.capability(name).is_some(),
"factory_name_for_kind({kind:?}) = {name:?}, but the factory has no such name"
);
}
let menu_item = crate::widget::capability::factory_name_for_kind(WidgetKind::MenuItem);
assert!(!menu_item.is_empty(), "MenuItem must still be nameable for theme classification");
assert_eq!(menu_item, "menu_item");
}
#[test]
fn a_kind_alias_reaches_the_same_capability_as_its_canonical_spelling() {
let factory = WidgetFactory::new_with_defaults();
let by_alias = factory.capability("context_menu").expect("context_menu must resolve");
let by_name = factory.capability("menu").expect("menu must resolve");
assert_eq!(by_alias.canonical_name, by_name.canonical_name);
assert_eq!(by_alias.kind, by_name.kind);
}
#[test]
fn capability_manifest_exports_defaults_and_metadata() {
let factory = WidgetFactory::new_with_defaults();
let manifest =
factory.capability_manifest("table").expect("table manifest should be exportable");
assert_eq!(manifest.kind, WidgetKind::Table);
assert_eq!(manifest.canonical_name, "table");
assert!(
manifest.aliases.is_empty(),
"`table` needs no alias: `normalize_key` already accepts `table_widget` / \
`tablewidget`, so an alias row would be a no-op (found: {:?})",
manifest.aliases
);
assert!(factory.capability("table_widget").is_some());
assert!(factory.capability("tablewidget").is_some());
assert!(manifest.events.iter().any(|event| event.name == "selection_changed"));
assert!(manifest.commands.contains(&"clear_selection"));
let has_model = manifest
.properties
.iter()
.find(|entry| entry.schema.name == "has_model")
.expect("has_model schema should exist");
assert_eq!(has_model.default_value, CapabilityValue::Bool(false));
let selection_mode = manifest
.properties
.iter()
.find(|entry| entry.schema.name == "selection_mode")
.expect("selection_mode schema should exist");
assert_eq!(selection_mode.default_value, CapabilityValue::String("single".to_string()));
}
#[test]
fn virtual_list_capability_read_write_roundtrip() {
let factory = WidgetFactory::new_with_defaults();
let mut list = VirtualList::new(Rect::new(0, 0, 120, 60));
assert_eq!(factory.read_property(&list, "has_data_source"), Ok(CapabilityValue::Bool(false)));
assert_eq!(factory.read_property(&list, "selected_row"), Ok(CapabilityValue::Null));
factory
.write_property(&mut list, "row_height", CapabilityValue::UInt(32))
.expect("row_height should be writable");
factory
.write_property(&mut list, "overscan", CapabilityValue::UInt(4))
.expect("overscan should be writable");
factory
.write_property(&mut list, "scroll_row", CapabilityValue::UInt(7))
.expect("scroll_row should be writable");
assert_eq!(factory.read_property(&list, "row_height"), Ok(CapabilityValue::UInt(32)));
assert_eq!(factory.read_property(&list, "overscan"), Ok(CapabilityValue::UInt(4)));
assert_eq!(factory.read_property(&list, "scroll_row"), Ok(CapabilityValue::UInt(0)));
}
#[test]
fn test_create_nonexistent_widget_returns_error() {
let factory = WidgetFactory::new_with_defaults();
assert!(factory.create("nonexistent", Rect::new(0, 0, 1, 1), "").is_none());
let empty = WidgetFactory::new();
assert!(empty.create("btn", Rect::new(0, 0, 1, 1), "").is_none());
}
#[test]
fn test_read_property_unregistered_widget_returns_error() {
let factory = WidgetFactory::new_with_defaults();
let widget = factory.create("btn", Rect::new(0, 0, 100, 30), "Save").unwrap();
let result = factory.read_property(widget.as_ref(), "__bogus_prop__");
assert_eq!(result, Err(CapabilityAccessError::UnknownProperty));
}
#[test]
fn unregistered_controls_are_constructible_by_canonical_name_and_alias() {
let factory = WidgetFactory::new_with_defaults();
let rect = Rect::new(0, 0, 240, 160);
let cases: &[(&str, &str, WidgetKind)] = &[
("timeline_widget", "timeline", WidgetKind::Chart),
("timeline_view", "timeline_view", WidgetKind::Chart),
("command_palette", "command_box", WidgetKind::ListView),
("notification_center", "notifications", WidgetKind::ListView),
("diff_viewer", "diff", WidgetKind::Table),
("markdown_editor", "md_editor", WidgetKind::RichEdit),
("toast_stack", "toasts", WidgetKind::PopupWindow),
("grid_table", "gridtable", WidgetKind::GridTable),
("number_picker", "picker", WidgetKind::NumberPicker),
("otp_input", "otp", WidgetKind::OtpInput),
("banner", "notice", WidgetKind::Banner),
("pagination", "page_numbers", WidgetKind::Pagination),
("radar_chart", "radar", WidgetKind::RadarChart),
("kanban_board", "kanban", WidgetKind::KanbanBoard),
("cascader", "cascade", WidgetKind::Cascader),
("query_builder", "filter_builder", WidgetKind::QueryBuilder),
("emoji_picker", "emoji", WidgetKind::EmojiPicker),
("mention", "at_mention", WidgetKind::Mention),
];
for (name, alias, kind) in cases {
let widget = factory
.create(name, rect, "")
.unwrap_or_else(|| panic!("{name} must be constructible through the factory"));
assert_eq!(widget.kind(), *kind, "{name} reported the wrong kind");
let via_alias = factory
.create(alias, rect, "")
.unwrap_or_else(|| panic!("{alias} must resolve to the same control as {name}"));
assert_eq!(via_alias.kind(), *kind, "{alias} reported the wrong kind");
}
}
#[test]
fn unregistered_controls_publish_their_own_properties() {
let factory = WidgetFactory::new_with_defaults();
let rect = Rect::new(0, 0, 240, 160);
let cases: &[(&str, &str)] = &[
("timeline_widget", "item_count"),
("command_palette", "filtered_count"),
("notification_center", "unread_count"),
("diff_viewer", "change_count"),
("markdown_editor", "word_count"),
("toast_stack", "toast_count"),
("grid_table", "row_count"),
("number_picker", "row_count"),
("otp_input", "length"),
("banner", "action_count"),
("pagination", "page_count"),
];
for (name, property) in cases {
let widget = factory
.create(name, rect, "")
.unwrap_or_else(|| panic!("{name} must be constructible through the factory"));
let value = factory.read_property(widget.as_ref(), property).unwrap_or_else(|error| {
panic!("{name} must answer its own property {property}, got {error:?}")
});
assert!(
matches!(value, CapabilityValue::UInt(_)),
"{name}::{property} must report a count, got {value:?}"
);
}
}
#[test]
fn unregistered_controls_round_trip_writable_properties() {
let factory = WidgetFactory::new_with_defaults();
let rect = Rect::new(0, 0, 240, 160);
let mut timeline = factory.create("timeline_widget", rect, "").expect("timeline");
factory
.write_property(timeline.as_mut(), "row_height", CapabilityValue::UInt(32))
.expect("timeline row_height should be writable");
assert_eq!(
factory.read_property(timeline.as_ref(), "row_height"),
Ok(CapabilityValue::UInt(32))
);
let mut palette = factory.create("command_palette", rect, "").expect("palette");
factory
.write_property(palette.as_mut(), "query", CapabilityValue::String("open".to_string()))
.expect("palette query should be writable");
assert_eq!(
factory.read_property(palette.as_ref(), "query"),
Ok(CapabilityValue::String("open".to_string()))
);
let mut diff = factory.create("diff_viewer", rect, "").expect("diff");
factory
.write_property(diff.as_mut(), "left_text", CapabilityValue::String("a".to_string()))
.expect("diff left_text should be writable");
factory
.write_property(diff.as_mut(), "right_text", CapabilityValue::String("b".to_string()))
.expect("diff right_text should be writable");
assert_eq!(
factory.read_property(diff.as_ref(), "left_text"),
Ok(CapabilityValue::String("a".to_string()))
);
assert_eq!(
factory.read_property(diff.as_ref(), "right_text"),
Ok(CapabilityValue::String("b".to_string()))
);
let mut markdown = factory.create("markdown_editor", rect, "").expect("markdown editor");
factory
.write_property(markdown.as_mut(), "preview_mode", CapabilityValue::Bool(true))
.expect("markdown preview_mode should be writable");
assert_eq!(
factory.read_property(markdown.as_ref(), "preview_mode"),
Ok(CapabilityValue::Bool(true))
);
let mut toasts = factory.create("toast_stack", rect, "").expect("toast stack");
factory
.write_property(toasts.as_mut(), "row_height", CapabilityValue::UInt(48))
.expect("toast row_height should be writable");
assert_eq!(factory.read_property(toasts.as_ref(), "row_height"), Ok(CapabilityValue::UInt(48)));
let mut grid_table = factory.create("grid_table", rect, "").expect("grid table");
factory
.write_property(grid_table.as_mut(), "row_height", CapabilityValue::UInt(36))
.expect("grid table row_height should be writable");
assert_eq!(
factory.read_property(grid_table.as_ref(), "row_height"),
Ok(CapabilityValue::UInt(36))
);
factory
.write_property(
grid_table.as_mut(),
"selection_mode",
CapabilityValue::String("row".to_string()),
)
.expect("grid table selection_mode should be writable");
assert_eq!(
factory.read_property(grid_table.as_ref(), "selection_mode"),
Ok(CapabilityValue::String("row".to_string()))
);
}
#[test]
fn timeline_viewport_write_never_inverts_the_range() {
let factory = WidgetFactory::new_with_defaults();
let mut timeline = factory
.create("timeline_widget", Rect::new(0, 0, 240, 160), "")
.expect("timeline must be constructible");
factory
.write_property(timeline.as_mut(), "viewport_start", CapabilityValue::Int(50))
.expect("viewport_start should be writable");
factory
.write_property(timeline.as_mut(), "viewport_end", CapabilityValue::Int(10))
.expect("viewport_end should be writable");
let start = match factory.read_property(timeline.as_ref(), "viewport_start") {
Ok(CapabilityValue::Int(value)) => value,
other => panic!("viewport_start must read back as Int, got {other:?}"),
};
let end = match factory.read_property(timeline.as_ref(), "viewport_end") {
Ok(CapabilityValue::Int(value)) => value,
other => panic!("viewport_end must read back as Int, got {other:?}"),
};
assert!(end > start, "an inverted write must be clamped: start={start}, end={end}");
}
#[test]
fn derived_names_reject_writes_on_newly_registered_controls() {
let factory = WidgetFactory::new_with_defaults();
let rect = Rect::new(0, 0, 240, 160);
let cases: &[(&str, &str, CapabilityValue)] = &[
("timeline_widget", "item_count", CapabilityValue::UInt(1)),
("command_palette", "filtered_count", CapabilityValue::UInt(1)),
("notification_center", "unread_count", CapabilityValue::UInt(1)),
("diff_viewer", "change_count", CapabilityValue::UInt(1)),
("markdown_editor", "word_count", CapabilityValue::UInt(1)),
("toast_stack", "toast_count", CapabilityValue::UInt(1)),
("grid_table", "row_count", CapabilityValue::UInt(1)),
("otp_input", "is_complete", CapabilityValue::Bool(true)),
("banner", "dismissed", CapabilityValue::Bool(true)),
("pagination", "page_count", CapabilityValue::UInt(1)),
];
for (name, property, value) in cases {
let mut widget = factory
.create(name, rect, "")
.unwrap_or_else(|| panic!("{name} must be constructible through the factory"));
assert_eq!(
factory.write_property(widget.as_mut(), property, value.clone()),
Err(CapabilityAccessError::ReadOnlyProperty),
"{name}::{property} must be declared read-only"
);
}
}
#[test]
fn declared_visible_default_matches_the_constructed_control() {
let factory = WidgetFactory::new_with_defaults();
let rect = Rect::new(0, 0, 200, 60);
let mut checked = 0usize;
for capability in factory.capabilities() {
let name = capability.canonical_name;
let Ok(declared) = factory.default_property_value(name, "visible") else {
continue;
};
let Some(widget) = factory.create(name, rect, "") else {
continue;
};
let Ok(live) = factory.read_property(widget.as_ref(), "visible") else {
continue;
};
assert_eq!(
declared, live,
"`{name}` declares `visible` default {declared:?} but a freshly \
constructed control reports {live:?}"
);
checked += 1;
}
assert!(checked >= 20, "expected the `visible` default to cover many kinds, got {checked}");
}
#[test]
fn declared_defaults_are_published_properties() {
let factory = WidgetFactory::new_with_defaults();
let mut violations = Vec::new();
for capability in factory.capabilities() {
let name = capability.canonical_name;
for property in capability.properties {
let Ok(value) = factory.default_property_value(name, property.name) else {
continue;
};
let published = capability
.properties
.iter()
.any(|schema| normalize_key(schema.name) == normalize_key(property.name));
if !published {
violations.push(format!("{name}::{} -> {value:?}", property.name));
}
}
}
assert!(violations.is_empty(), "defaults declared for unpublished properties: {violations:?}");
}