use nalgebra_glm::{Vec2, Vec4};
use winit::keyboard::KeyCode;
use crate::ecs::ui::resources::UiEvent;
use crate::ecs::world::World;
use super::types::{AssertKind, EventMatch, ScheduledAction, TestAction, UiTestResult};
pub struct UiTestDriver {
pub(super) actions: Vec<ScheduledAction>,
pub(super) frame: u64,
pub(super) frame_cursor: u64,
pub(super) results: Vec<UiTestResult>,
pub(super) captured_events: Vec<UiEvent>,
pub(super) active: bool,
pub(super) exit_on_complete: bool,
pub(super) cursor: usize,
pub(super) pending_release: bool,
pub(super) pending_right_release: bool,
pub(super) pending_key_releases: Vec<KeyCode>,
pub(super) test_name: String,
}
impl UiTestDriver {
pub fn new(name: &str) -> Self {
Self {
actions: Vec::new(),
frame: 0,
frame_cursor: 0,
results: Vec::new(),
captured_events: Vec::new(),
active: false,
exit_on_complete: true,
cursor: 0,
pending_release: false,
pending_right_release: false,
pending_key_releases: Vec::new(),
test_name: name.to_string(),
}
}
pub fn set_exit_on_complete(&mut self, exit: bool) {
self.exit_on_complete = exit;
}
pub fn start(&mut self) {
self.active = true;
self.frame = 0;
self.cursor = 0;
self.results.clear();
self.captured_events.clear();
self.actions.sort_by_key(|a| a.frame);
}
pub fn is_active(&self) -> bool {
self.active
}
pub fn results(&self) -> &[UiTestResult] {
&self.results
}
pub fn all_passed(&self) -> bool {
self.results.iter().all(|r| r.passed)
}
pub(super) fn schedule(&mut self, frame: u64, action: TestAction) {
self.actions.push(ScheduledAction { frame, action });
}
pub fn at_frame(&mut self, frame: u64) -> ActionBuilder<'_> {
self.frame_cursor = frame;
ActionBuilder {
driver: self,
frame,
}
}
pub fn wait(&mut self, frames: u64) -> ActionBuilder<'_> {
self.frame_cursor += frames;
let frame = self.frame_cursor;
ActionBuilder {
driver: self,
frame,
}
}
pub fn then(&mut self) -> ActionBuilder<'_> {
let frame = self.frame_cursor;
ActionBuilder {
driver: self,
frame,
}
}
pub fn find_entity(&self, world: &World, test_id: &str) -> Option<freecs::Entity> {
world
.resources
.retained_ui
.test_id_map
.get(test_id)
.copied()
}
pub fn entity_center(&self, world: &World, test_id: &str) -> Option<Vec2> {
self.resolve_entity_center(world, test_id)
}
pub fn record_pass(&mut self, name: &str, message: &str) {
self.results.push(UiTestResult {
name: name.to_string(),
passed: true,
message: message.to_string(),
});
}
pub fn record_fail(&mut self, name: &str, message: &str) {
self.results.push(UiTestResult {
name: name.to_string(),
passed: false,
message: message.to_string(),
});
}
pub fn record_assert(&mut self, name: &str, passed: bool, message: &str) {
self.results.push(UiTestResult {
name: name.to_string(),
passed,
message: message.to_string(),
});
}
}
pub struct ActionBuilder<'a> {
driver: &'a mut UiTestDriver,
frame: u64,
}
impl ActionBuilder<'_> {
pub fn move_mouse(self, position: Vec2) -> Self {
self.driver
.schedule(self.frame, TestAction::MoveMouse(position));
self
}
pub fn move_mouse_to(self, test_id: &str) -> Self {
self.driver.schedule(
self.frame,
TestAction::MoveMouseToEntity(test_id.to_string()),
);
self
}
pub fn click(self, position: Vec2) -> Self {
self.driver
.schedule(self.frame, TestAction::ClickAt(position));
self
}
pub fn click_entity(self, test_id: &str) -> Self {
self.driver
.schedule(self.frame, TestAction::ClickAtEntity(test_id.to_string()));
self
}
pub fn right_click(self, position: Vec2) -> Self {
self.driver
.schedule(self.frame, TestAction::RightClickAt(position));
self
}
pub fn right_click_entity(self, test_id: &str) -> Self {
self.driver.schedule(
self.frame,
TestAction::RightClickAtEntity(test_id.to_string()),
);
self
}
pub fn drag_entity(self, test_id: &str, end_position: Vec2) -> Self {
self.driver.schedule(
self.frame,
TestAction::DragEntity(test_id.to_string(), end_position),
);
self
}
pub fn set_slider_value(self, test_id: &str, value: f32) -> Self {
self.driver.schedule(
self.frame,
TestAction::SetSliderValue(test_id.to_string(), value),
);
self
}
pub fn select_dropdown_option(self, test_id: &str, option_index: usize) -> Self {
self.driver.schedule(
self.frame,
TestAction::SelectDropdownOption(test_id.to_string(), option_index),
);
self
}
pub fn select_tab(self, test_id: &str, tab_index: usize) -> Self {
self.driver.schedule(
self.frame,
TestAction::SelectTab(test_id.to_string(), tab_index),
);
self
}
pub fn mouse_press(self) -> Self {
self.driver.schedule(self.frame, TestAction::MousePress);
self
}
pub fn mouse_release(self) -> Self {
self.driver.schedule(self.frame, TestAction::MouseRelease);
self
}
pub fn right_mouse_press(self) -> Self {
self.driver
.schedule(self.frame, TestAction::RightMousePress);
self
}
pub fn right_mouse_release(self) -> Self {
self.driver
.schedule(self.frame, TestAction::RightMouseRelease);
self
}
pub fn type_text(self, text: &str) -> Self {
let chars: Vec<char> = text.chars().collect();
self.driver
.schedule(self.frame, TestAction::TypeChars(chars));
self
}
pub fn press_key(self, key: KeyCode) -> Self {
self.driver
.schedule(self.frame, TestAction::PressAndReleaseKey(key));
self
}
pub fn key_down(self, key: KeyCode) -> Self {
self.driver.schedule(self.frame, TestAction::KeyDown(key));
self
}
pub fn key_up(self, key: KeyCode) -> Self {
self.driver.schedule(self.frame, TestAction::KeyUp(key));
self
}
pub fn scroll(self, delta: Vec2) -> Self {
self.driver.schedule(self.frame, TestAction::Scroll(delta));
self
}
pub fn screenshot(self, path: &str) -> Self {
self.driver
.schedule(self.frame, TestAction::Screenshot(path.to_string()));
self
}
pub fn log(self, message: &str) -> Self {
self.driver
.schedule(self.frame, TestAction::Log(message.to_string()));
self
}
pub fn assert_visible(self, test_id: &str) -> Self {
let name = format!("{test_id} visible");
self.driver.schedule(
self.frame,
TestAction::Assert(name, AssertKind::Visible(test_id.to_string())),
);
self
}
pub fn assert_not_visible(self, test_id: &str) -> Self {
let name = format!("{test_id} not visible");
self.driver.schedule(
self.frame,
TestAction::Assert(name, AssertKind::NotVisible(test_id.to_string())),
);
self
}
pub fn assert_focused(self, test_id: &str) -> Self {
let name = format!("{test_id} focused");
self.driver.schedule(
self.frame,
TestAction::Assert(name, AssertKind::Focused(test_id.to_string())),
);
self
}
pub fn assert_not_focused(self, test_id: &str) -> Self {
let name = format!("{test_id} not focused");
self.driver.schedule(
self.frame,
TestAction::Assert(name, AssertKind::NotFocused(test_id.to_string())),
);
self
}
pub fn assert_hovered(self, test_id: &str) -> Self {
let name = format!("{test_id} hovered");
self.driver.schedule(
self.frame,
TestAction::Assert(name, AssertKind::Hovered(test_id.to_string())),
);
self
}
pub fn assert_not_hovered(self, test_id: &str) -> Self {
let name = format!("{test_id} not hovered");
self.driver.schedule(
self.frame,
TestAction::Assert(name, AssertKind::NotHovered(test_id.to_string())),
);
self
}
pub fn assert_disabled(self, test_id: &str) -> Self {
let name = format!("{test_id} disabled");
self.driver.schedule(
self.frame,
TestAction::Assert(name, AssertKind::Disabled(test_id.to_string())),
);
self
}
pub fn assert_not_disabled(self, test_id: &str) -> Self {
let name = format!("{test_id} enabled");
self.driver.schedule(
self.frame,
TestAction::Assert(name, AssertKind::NotDisabled(test_id.to_string())),
);
self
}
pub fn assert_text(self, test_id: &str, expected: &str) -> Self {
let name = format!("{test_id} text");
self.driver.schedule(
self.frame,
TestAction::Assert(
name,
AssertKind::TextEquals(test_id.to_string(), expected.to_string()),
),
);
self
}
pub fn assert_value_f32(self, prop_name: &str, expected: f32) -> Self {
let name = format!("{prop_name} value");
self.driver.schedule(
self.frame,
TestAction::Assert(
name,
AssertKind::WidgetValueF32(prop_name.to_string(), expected, 0.01),
),
);
self
}
pub fn assert_value_f32_approx(self, prop_name: &str, expected: f32, tolerance: f32) -> Self {
let name = format!("{prop_name} value (approx)");
self.driver.schedule(
self.frame,
TestAction::Assert(
name,
AssertKind::WidgetValueF32(prop_name.to_string(), expected, tolerance),
),
);
self
}
pub fn assert_value_bool(self, prop_name: &str, expected: bool) -> Self {
let name = format!("{prop_name} value");
self.driver.schedule(
self.frame,
TestAction::Assert(
name,
AssertKind::WidgetValueBool(prop_name.to_string(), expected),
),
);
self
}
pub fn assert_value_usize(self, prop_name: &str, expected: usize) -> Self {
let name = format!("{prop_name} value");
self.driver.schedule(
self.frame,
TestAction::Assert(
name,
AssertKind::WidgetValueUsize(prop_name.to_string(), expected),
),
);
self
}
pub fn assert_value_string(self, prop_name: &str, expected: &str) -> Self {
let name = format!("{prop_name} value");
self.driver.schedule(
self.frame,
TestAction::Assert(
name,
AssertKind::WidgetValueString(prop_name.to_string(), expected.to_string()),
),
);
self
}
pub fn assert_value_vec4(self, prop_name: &str, expected: Vec4, tolerance: f32) -> Self {
let name = format!("{prop_name} value");
self.driver.schedule(
self.frame,
TestAction::Assert(
name,
AssertKind::WidgetValueVec4(prop_name.to_string(), expected, tolerance),
),
);
self
}
fn assert_event(self, name: &str, event_match: EventMatch) -> Self {
self.driver.schedule(
self.frame,
TestAction::Assert(name.to_string(), AssertKind::EventFired(event_match)),
);
self
}
pub fn assert_event_fired_button_clicked(self) -> Self {
self.assert_event("button clicked event", EventMatch::ButtonClicked)
}
pub fn assert_event_fired_slider_changed(self) -> Self {
self.assert_event("slider changed event", EventMatch::SliderChanged)
}
pub fn assert_event_fired_toggle_changed(self) -> Self {
self.assert_event("toggle changed event", EventMatch::ToggleChanged)
}
pub fn assert_event_fired_text_submitted(self) -> Self {
self.assert_event("text input submitted event", EventMatch::TextInputSubmitted)
}
pub fn assert_event_fired_text_changed(self) -> Self {
self.assert_event("text input changed event", EventMatch::TextInputChanged)
}
pub fn assert_event_fired_checkbox_changed(self) -> Self {
self.assert_event("checkbox changed event", EventMatch::CheckboxChanged)
}
pub fn assert_event_fired_dropdown_changed(self) -> Self {
self.assert_event("dropdown changed event", EventMatch::DropdownChanged)
}
pub fn assert_event_fired_radio_changed(self) -> Self {
self.assert_event("radio changed event", EventMatch::RadioChanged)
}
pub fn assert_event_fired_tab_changed(self) -> Self {
self.assert_event("tab changed event", EventMatch::TabChanged)
}
pub fn assert_event_fired_drag_value_changed(self) -> Self {
self.assert_event("drag value changed event", EventMatch::DragValueChanged)
}
pub fn assert_event_fired_menu_item_clicked(self) -> Self {
self.assert_event("menu item clicked event", EventMatch::MenuItemClicked)
}
pub fn assert_event_fired_color_picker_changed(self) -> Self {
self.assert_event("color picker changed event", EventMatch::ColorPickerChanged)
}
pub fn assert_event_fired_selectable_label_clicked(self) -> Self {
self.assert_event(
"selectable label clicked event",
EventMatch::SelectableLabelClicked,
)
}
pub fn assert_event_fired_context_menu_item_clicked(self) -> Self {
self.assert_event(
"context menu item clicked event",
EventMatch::ContextMenuItemClicked,
)
}
pub fn assert_event_fired_tree_node_selected(self) -> Self {
self.assert_event("tree node selected event", EventMatch::TreeNodeSelected)
}
pub fn assert_event_fired_tree_node_toggled(self) -> Self {
self.assert_event("tree node toggled event", EventMatch::TreeNodeToggled)
}
pub fn assert_event_fired_modal_closed(self) -> Self {
self.assert_event("modal closed event", EventMatch::ModalClosed)
}
pub fn assert_event_fired_command_palette_executed(self) -> Self {
self.assert_event(
"command palette executed event",
EventMatch::CommandPaletteExecuted,
)
}
pub fn assert_event_fired_tile_tab_activated(self) -> Self {
self.assert_event("tile tab activated event", EventMatch::TileTabActivated)
}
pub fn assert_event_fired_tile_tab_closed(self) -> Self {
self.assert_event("tile tab closed event", EventMatch::TileTabClosed)
}
pub fn assert_event_fired_tile_splitter_moved(self) -> Self {
self.assert_event("tile splitter moved event", EventMatch::TileSplitterMoved)
}
pub fn assert_event_fired_canvas_clicked(self) -> Self {
self.assert_event("canvas clicked event", EventMatch::CanvasClicked)
}
pub fn assert_event_fired_virtual_list_item_clicked(self) -> Self {
self.assert_event(
"virtual list item clicked event",
EventMatch::VirtualListItemClicked,
)
}
pub fn assert_event_fired_text_area_changed(self) -> Self {
self.assert_event("text area changed event", EventMatch::TextAreaChanged)
}
pub fn assert_event_fired_rich_text_editor_changed(self) -> Self {
self.assert_event(
"rich text editor changed event",
EventMatch::RichTextEditorChanged,
)
}
pub fn assert_event_fired_data_grid_filter_changed(self) -> Self {
self.assert_event(
"data grid filter changed event",
EventMatch::DataGridFilterChanged,
)
}
pub fn assert_event_fired_range_slider_changed(self) -> Self {
self.assert_event("range slider changed event", EventMatch::RangeSliderChanged)
}
pub fn assert_event_fired_data_grid_cell_edited(self) -> Self {
self.assert_event(
"data grid cell edited event",
EventMatch::DataGridCellEdited,
)
}
pub fn assert_event_fired_breadcrumb_clicked(self) -> Self {
self.assert_event("breadcrumb clicked event", EventMatch::BreadcrumbClicked)
}
pub fn assert_event_fired_splitter_changed(self) -> Self {
self.assert_event("splitter changed event", EventMatch::SplitterChanged)
}
pub fn assert_event_fired_multi_select_changed(self) -> Self {
self.assert_event("multi select changed event", EventMatch::MultiSelectChanged)
}
pub fn assert_event_fired_date_picker_changed(self) -> Self {
self.assert_event("date picker changed event", EventMatch::DatePickerChanged)
}
pub fn assert_event_fired_drag_started(self) -> Self {
self.assert_event("drag started event", EventMatch::DragStarted)
}
pub fn assert_event_fired_drag_dropped(self) -> Self {
self.assert_event("drag dropped event", EventMatch::DragDropped)
}
pub fn assert_event_fired_drag_cancelled(self) -> Self {
self.assert_event("drag cancelled event", EventMatch::DragCancelled)
}
pub fn exit_pass(self) -> Self {
self.driver.schedule(self.frame, TestAction::Exit(true));
self
}
pub fn exit_fail(self) -> Self {
self.driver.schedule(self.frame, TestAction::Exit(false));
self
}
}
pub fn setup_test_scene(world: &mut World) {
world.resources.retained_ui.enabled = true;
world.resources.retained_ui.background_color =
Some(nalgebra_glm::Vec4::new(0.05, 0.05, 0.08, 1.0));
let camera =
crate::ecs::camera::commands::spawn_ortho_camera(world, nalgebra_glm::Vec2::new(0.0, 0.0));
world.resources.active_camera = Some(camera);
}
pub fn create_test_root(tree: &mut crate::ecs::ui::builder::UiTreeBuilder<'_>) -> freecs::Entity {
tree.add_node()
.boundary(
crate::ecs::ui::units::Ab(Vec2::new(20.0, 20.0)),
crate::ecs::ui::units::Ab(Vec2::new(-20.0, -20.0))
+ crate::ecs::ui::units::Rl(Vec2::new(100.0, 100.0)),
)
.flow(
crate::ecs::ui::layout_types::FlowDirection::Vertical,
8.0,
8.0,
)
.entity()
}