1use crate::message::{ButtonState, KeyCode, OsEvent};
22use crate::{
23 core::{algebra::Vector2, info, pool::Handle, reflect::Reflect},
24 message::{MessageData, UiMessage},
25 text::Text,
26 widget::WidgetMessage,
27 BuildContext, Control, UiNode, UserInterface,
28};
29use fyrox_core::err;
30use fyrox_core::pool::ObjectOrVariant;
31use fyrox_graph::{SceneGraph, SceneGraphNode};
32use uuid::Uuid;
33
34pub trait UserInterfaceTestingExtension {
35 fn click(&mut self, position: Vector2<f32>);
37
38 fn click_at(&mut self, uuid: Uuid) -> Handle<UiNode>;
40
41 fn click_at_text(&mut self, uuid: Uuid, text: &str);
42
43 fn find_by_uuid(&self, uuid: Uuid) -> Option<&UiNode>;
44
45 fn find_by_uuid_of<T: Control>(&self, uuid: Uuid) -> Option<&T>;
46
47 fn is_visible(&self, uuid: Uuid) -> bool {
48 if let Some(node) = self.find_by_uuid(uuid) {
49 node.is_globally_visible()
50 } else {
51 panic!("Widget {uuid} does not exist!")
52 }
53 }
54
55 fn poll_all_messages(&mut self);
56
57 fn poll_and_count(&mut self, pred: impl FnMut(&UiMessage) -> bool) -> usize;
58
59 fn click_at_count_response<M: MessageData + PartialEq>(
60 &mut self,
61 name: Uuid,
62 response: M,
63 ) -> usize;
64
65 fn type_text(&mut self, text: &str);
66}
67
68fn is_enabled(mut handle: Handle<UiNode>, ui: &UserInterface) -> bool {
69 while let Ok(node) = ui.try_get(handle) {
70 if !node.enabled() {
71 return false;
72 }
73 handle = node.parent();
74 }
75 true
76}
77
78impl UserInterfaceTestingExtension for UserInterface {
79 fn click(&mut self, position: Vector2<f32>) {
80 self.process_os_event(&crate::message::OsEvent::CursorMoved { position });
81 self.process_os_event(&crate::message::OsEvent::MouseInput {
82 button: crate::message::MouseButton::Left,
83 state: crate::message::ButtonState::Pressed,
84 });
85 self.process_os_event(&crate::message::OsEvent::MouseInput {
86 button: crate::message::MouseButton::Left,
87 state: crate::message::ButtonState::Released,
88 });
89 }
90
91 fn click_at(&mut self, uuid: Uuid) -> Handle<UiNode> {
92 assert_ne!(uuid, Uuid::default());
93 if let Some((handle, n)) = self.find_from_root(&mut |n| n.id == uuid) {
94 info!("{} - bounds {:?}", uuid, n.screen_bounds());
95 assert!(is_enabled(handle, self));
96 assert!(n.is_globally_visible());
97 let center = n.screen_bounds().center();
98 self.click(center);
99 info!(
100 "==== Clicked at {uuid}({}:{}) at [{};{}] coords. ====",
101 handle.index(),
102 handle.generation(),
103 center.x,
104 center.y
105 );
106 handle
107 } else {
108 panic!("There's no widget {uuid}!")
109 }
110 }
111
112 fn click_at_text(&mut self, uuid: Uuid, text: &str) {
113 assert_ne!(uuid, Uuid::default());
114 if let Some((start_handle, start_node)) = self.find_from_root(&mut |n| n.id == uuid) {
115 info!("{} - bounds {:?}", uuid, start_node.screen_bounds());
116 assert!(is_enabled(start_handle, self));
117 assert!(start_node.is_globally_visible());
118 if let Some((text_handle, text_node)) = self.find(start_handle, &mut |n| {
119 if let Some(text_widget) = n.component_ref::<Text>() {
120 text_widget.text() == text
121 } else {
122 false
123 }
124 }) {
125 assert!(is_enabled(text_handle, self));
126 assert!(text_node.is_globally_visible());
127 let center = text_node.screen_bounds().center();
128 self.click(center);
129 info!(
130 "==== Clicked at {text}({}:{}) at [{};{}] coords. Found from {uuid} starting location. ====",
131 text_handle.index(),
132 text_handle.generation(),
133 center.x,
134 center.y
135 );
136 }
137 } else {
138 panic!("There's no widget {uuid}!")
139 }
140 }
141
142 fn click_at_count_response<M: MessageData + PartialEq>(
143 &mut self,
144 uuid: Uuid,
145 response: M,
146 ) -> usize {
147 let handle = self.click_at(uuid);
148 self.poll_and_count(move |msg| msg.data_from::<M>(handle) == Some(&response))
149 }
150
151 fn find_by_uuid(&self, uuid: Uuid) -> Option<&UiNode> {
152 self.find_from_root(&mut |n| n.id == uuid).map(|(_, n)| n)
153 }
154
155 fn find_by_uuid_of<T: Control>(&self, uuid: Uuid) -> Option<&T> {
156 self.find_from_root(&mut |n| n.id == uuid)
157 .and_then(|(_, n)| n.cast())
158 }
159
160 fn poll_all_messages(&mut self) {
161 while let Some(msg) = self.poll_message() {
162 if let Ok(widget) = self.try_get(msg.destination()) {
163 let ty = Reflect::type_name(widget);
164 info!("[{ty}]{msg:?}");
165 }
166 }
167 let screen_size = self.screen_size();
168 self.update(screen_size, 1.0 / 60.0, &Default::default());
169 }
170
171 fn poll_and_count(&mut self, mut pred: impl FnMut(&UiMessage) -> bool) -> usize {
172 let mut num = 0;
173 while let Some(msg) = self.poll_message() {
174 if let Ok(widget) = self.try_get(msg.destination()) {
175 let ty = Reflect::type_name(widget);
176 info!("[{ty}]{msg:?}");
177 }
178
179 if pred(&msg) {
180 num += 1;
181 }
182 }
183 let screen_size = self.screen_size();
184 self.update(screen_size, 1.0 / 60.0, &Default::default());
185 num
186 }
187
188 fn type_text(&mut self, text: &str) {
189 for char in text.chars() {
190 if char.is_ascii() {
191 match KeyCode::try_from(char) {
192 Ok(button) => {
193 self.process_os_event(&OsEvent::KeyboardInput {
194 button,
195 state: ButtonState::Pressed,
196 text: char.to_string(),
197 });
198 self.process_os_event(&OsEvent::KeyboardInput {
199 button,
200 state: ButtonState::Released,
201 text: Default::default(),
202 });
203 }
204 Err(err) => {
205 err!(
206 "Unable to emulate {} character press. Reason: {}",
207 char,
208 err
209 )
210 }
211 }
212 }
213 }
214 }
215}
216
217pub fn test_widget_deletion<F, U>(constructor: F)
218where
219 F: FnOnce(&mut BuildContext) -> Handle<U>,
220 U: ObjectOrVariant<UiNode>,
221{
222 let screen_size = Vector2::new(100.0, 100.0);
223 let mut ui = UserInterface::new(screen_size);
224 let widget = constructor(&mut ui.build_ctx());
225 ui.send(widget, WidgetMessage::Remove);
226 ui.update(screen_size, 1.0 / 60.0, &Default::default());
227 while ui.poll_message().is_some() {}
228 assert_eq!(ui.nodes().alive_count(), 1);
230}