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            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    // Only root node must be alive.
229    assert_eq!(ui.nodes().alive_count(), 1);
230}