Skip to main content

fyrox_ui/
test.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use 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    /// Clicks at the given position.
36    fn click(&mut self, position: Vector2<f32>);
37
38    /// Tries to find a widget with the given unique id and clicks at its center.
39    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            if self.visual_debug {
95                info!("{} - bounds {:?}", uuid, n.screen_bounds());
96            }
97            assert!(is_enabled(handle, self));
98            assert!(n.is_globally_visible());
99            let center = n.screen_bounds().center();
100            self.click(center);
101            if self.visual_debug {
102                info!(
103                    "==== Clicked at {uuid}({}:{}) at [{};{}] coords. ====",
104                    handle.index(),
105                    handle.generation(),
106                    center.x,
107                    center.y
108                );
109            }
110            handle
111        } else {
112            panic!("There's no widget {uuid}!")
113        }
114    }
115
116    fn click_at_text(&mut self, uuid: Uuid, text: &str) {
117        assert_ne!(uuid, Uuid::default());
118        if let Some((start_handle, start_node)) = self.find_from_root(&mut |n| n.id == uuid) {
119            if self.visual_debug {
120                info!("{} - bounds {:?}", uuid, start_node.screen_bounds());
121            }
122            assert!(is_enabled(start_handle, self));
123            assert!(start_node.is_globally_visible());
124            if let Some((text_handle, text_node)) = self.find(start_handle, &mut |n| {
125                if let Some(text_widget) = n.component_ref::<Text>() {
126                    text_widget.text() == text
127                } else {
128                    false
129                }
130            }) {
131                assert!(is_enabled(text_handle, self));
132                assert!(text_node.is_globally_visible());
133                let center = text_node.screen_bounds().center();
134                self.click(center);
135                if self.visual_debug {
136                    info!(
137                    "==== Clicked at {text}({}:{}) at [{};{}] coords. Found from {uuid} starting location. ====",
138                    text_handle.index(),
139                    text_handle.generation(),
140                    center.x,
141                    center.y
142                );
143                }
144            }
145        } else {
146            panic!("There's no widget {uuid}!")
147        }
148    }
149
150    fn click_at_count_response<M: MessageData + PartialEq>(
151        &mut self,
152        uuid: Uuid,
153        response: M,
154    ) -> usize {
155        let handle = self.click_at(uuid);
156        self.poll_and_count(move |msg| msg.data_from::<M>(handle) == Some(&response))
157    }
158
159    fn find_by_uuid(&self, uuid: Uuid) -> Option<&UiNode> {
160        self.find_from_root(&mut |n| n.id == uuid).map(|(_, n)| n)
161    }
162
163    fn find_by_uuid_of<T: Control>(&self, uuid: Uuid) -> Option<&T> {
164        self.find_from_root(&mut |n| n.id == uuid)
165            .and_then(|(_, n)| n.cast())
166    }
167
168    fn poll_all_messages(&mut self) {
169        while let Some(msg) = self.poll_message() {
170            if self.visual_debug {
171                if let Ok(widget) = self.try_get(msg.destination()) {
172                    let ty = Reflect::type_name(widget);
173                    info!("[{ty}]{msg:?}");
174                }
175            }
176        }
177        let screen_size = self.screen_size();
178        self.update(screen_size, 1.0 / 60.0, &Default::default());
179    }
180
181    fn poll_and_count(&mut self, mut pred: impl FnMut(&UiMessage) -> bool) -> usize {
182        let mut num = 0;
183        while let Some(msg) = self.poll_message() {
184            if self.visual_debug {
185                if let Ok(widget) = self.try_get(msg.destination()) {
186                    let ty = Reflect::type_name(widget);
187                    info!("[{ty}]{msg:?}");
188                }
189            }
190
191            if pred(&msg) {
192                num += 1;
193            }
194        }
195        let screen_size = self.screen_size();
196        self.update(screen_size, 1.0 / 60.0, &Default::default());
197        num
198    }
199
200    fn type_text(&mut self, text: &str) {
201        for char in text.chars() {
202            if char.is_ascii() {
203                match KeyCode::try_from(char) {
204                    Ok(button) => {
205                        self.process_os_event(&OsEvent::KeyboardInput {
206                            button,
207                            state: ButtonState::Pressed,
208                            text: char.to_string(),
209                        });
210                        self.process_os_event(&OsEvent::KeyboardInput {
211                            button,
212                            state: ButtonState::Released,
213                            text: Default::default(),
214                        });
215                    }
216                    Err(err) => {
217                        err!(
218                            "Unable to emulate {} character press. Reason: {}",
219                            char,
220                            err
221                        )
222                    }
223                }
224            }
225        }
226    }
227}
228
229pub fn test_widget_deletion<F, U>(constructor: F)
230where
231    F: FnOnce(&mut BuildContext) -> Handle<U>,
232    U: ObjectOrVariant<UiNode>,
233{
234    let screen_size = Vector2::new(100.0, 100.0);
235    let mut ui = UserInterface::new(screen_size);
236    let widget = constructor(&mut ui.build_ctx());
237    ui.send(widget, WidgetMessage::Remove);
238    ui.update(screen_size, 1.0 / 60.0, &Default::default());
239    while ui.poll_message().is_some() {}
240    // Only root node must be alive.
241    assert_eq!(ui.nodes().alive_count(), 1);
242}