freya_testing/
lib.rs

1use std::{
2    borrow::Cow,
3    cell::RefCell,
4    collections::HashMap,
5    fs::File,
6    io::Write,
7    path::PathBuf,
8    rc::Rc,
9    time::{
10        Duration,
11        Instant,
12    },
13};
14
15use freya_components::{
16    cache::AssetCacher,
17    integration::integration,
18};
19use freya_core::integration::*;
20pub use freya_core::{
21    events::platform::*,
22    prelude::*,
23};
24use freya_engine::prelude::{
25    EncodedImageFormat,
26    FontCollection,
27    FontMgr,
28    SkData,
29    TypefaceFontProvider,
30    raster_n32_premul,
31};
32use ragnarok::{
33    CursorPoint,
34    EventsExecutorRunner,
35    EventsMeasurerRunner,
36    NodesState,
37};
38use torin::prelude::{
39    LayoutNode,
40    Size2D,
41};
42
43pub mod prelude {
44    pub use crate::*;
45}
46
47pub fn launch_doc(app: impl Into<FpRender>, size: Size2D, path: impl Into<PathBuf>) {
48    launch_doc_hook(app, size, path, |_| {})
49}
50
51pub fn launch_doc_hook(
52    app: impl Into<FpRender>,
53    size: Size2D,
54    path: impl Into<PathBuf>,
55    hook: impl FnOnce(&mut TestingRunner),
56) {
57    let (mut test, _) = TestingRunner::new(app, size, |_| {});
58    hook(&mut test);
59    test.render_to_file(path);
60}
61
62pub fn launch_test(app: impl Into<FpRender>) -> TestingRunner {
63    TestingRunner::new(app, Size2D::new(500., 500.), |_| {}).0
64}
65
66pub struct TestingRunner {
67    nodes_state: NodesState<NodeId>,
68    runner: Runner,
69    tree: Rc<RefCell<Tree>>,
70    size: Size2D,
71
72    accessibility: AccessibilityTree,
73
74    events_receiver: futures_channel::mpsc::UnboundedReceiver<EventsChunk>,
75    events_sender: futures_channel::mpsc::UnboundedSender<EventsChunk>,
76
77    font_manager: FontMgr,
78    font_collection: FontCollection,
79
80    platform: Platform,
81
82    ticker_sender: RenderingTickerSender,
83
84    default_fonts: Vec<Cow<'static, str>>,
85}
86
87impl TestingRunner {
88    pub fn new<T>(
89        app: impl Into<FpRender>,
90        size: Size2D,
91        hook: impl FnOnce(&mut Runner) -> T,
92    ) -> (Self, T) {
93        let (events_sender, events_receiver) = futures_channel::mpsc::unbounded();
94        let app = app.into();
95        let mut runner = Runner::new(move || integration(app.clone()).into_element());
96
97        runner.provide_root_context(ScreenReader::new);
98
99        let (mut ticker_sender, ticker) = RenderingTicker::new();
100        ticker_sender.set_overflow(true);
101        runner.provide_root_context(|| ticker);
102
103        let animation_clock = AnimationClock::new();
104        runner.provide_root_context(|| animation_clock.clone());
105
106        runner.provide_root_context(AssetCacher::create);
107
108        let tree = Tree::default();
109        let tree = Rc::new(RefCell::new(tree));
110
111        let platform = runner.provide_root_context({
112            let tree = tree.clone();
113            || Platform {
114                focused_accessibility_id: State::create(ACCESSIBILITY_ROOT_ID),
115                focused_accessibility_node: State::create(accesskit::Node::new(
116                    accesskit::Role::Window,
117                )),
118                root_size: State::create(size),
119                navigation_mode: State::create(NavigationMode::NotKeyboard),
120                preferred_theme: State::create(PreferredTheme::Light),
121                sender: Rc::new(move |user_event| {
122                    match user_event {
123                        UserEvent::RequestRedraw => {
124                            // Nothing
125                        }
126                        UserEvent::FocusAccessibilityNode(strategy) => {
127                            tree.borrow_mut().accessibility_diff.request_focus(strategy);
128                        }
129                        UserEvent::SetCursorIcon(_) => {
130                            // Nothing
131                        }
132                        UserEvent::Erased(_) => {
133                            // Nothing
134                        }
135                    }
136                }),
137            }
138        });
139
140        runner.provide_root_context(|| tree.borrow().accessibility_generator.clone());
141
142        let hook_result = hook(&mut runner);
143
144        let mut font_collection = FontCollection::new();
145        let def_mgr = FontMgr::default();
146        let provider = TypefaceFontProvider::new();
147        let font_manager: FontMgr = provider.into();
148        font_collection.set_default_font_manager(def_mgr, None);
149        font_collection.set_dynamic_font_manager(font_manager.clone());
150        font_collection.paragraph_cache_mut().turn_on(false);
151
152        let mutations = runner.sync_and_update();
153        tree.borrow_mut().apply_mutations(mutations);
154        tree.borrow_mut().measure_layout(
155            size,
156            &font_collection,
157            &font_manager,
158            &events_sender,
159            1.0,
160            &default_fonts(),
161        );
162
163        let nodes_state = NodesState::default();
164        let accessibility = AccessibilityTree::default();
165
166        (
167            Self {
168                runner,
169                tree,
170                size,
171
172                accessibility,
173                platform,
174
175                nodes_state,
176                events_receiver,
177                events_sender,
178
179                font_manager,
180                font_collection,
181
182                ticker_sender,
183
184                default_fonts: default_fonts(),
185            },
186            hook_result,
187        )
188    }
189
190    pub fn set_fonts(&mut self, fonts: HashMap<&str, &[u8]>) {
191        let mut provider = TypefaceFontProvider::new();
192        for (font_name, font_data) in fonts {
193            let ft_type = self
194                .font_collection
195                .fallback_manager()
196                .unwrap()
197                .new_from_data(font_data, None)
198                .unwrap_or_else(|| panic!("Failed to load font {font_name}."));
199            provider.register_typeface(ft_type, Some(font_name));
200        }
201        let font_manager: FontMgr = provider.into();
202        self.font_manager = font_manager.clone();
203        self.font_collection.set_dynamic_font_manager(font_manager);
204    }
205
206    pub fn set_default_fonts(&mut self, fonts: &[Cow<'static, str>]) {
207        self.default_fonts.clear();
208        self.default_fonts.extend_from_slice(fonts);
209        self.tree.borrow_mut().layout.reset();
210        self.tree.borrow_mut().text_cache.reset();
211        self.tree.borrow_mut().measure_layout(
212            self.size,
213            &self.font_collection,
214            &self.font_manager,
215            &self.events_sender,
216            1.0,
217            &self.default_fonts,
218        );
219        self.tree.borrow_mut().accessibility_diff.clear();
220        self.accessibility.focused_id = ACCESSIBILITY_ROOT_ID;
221        self.accessibility.init(&mut self.tree.borrow_mut());
222        self.sync_and_update();
223    }
224
225    pub async fn handle_events(&mut self) {
226        self.runner.handle_events().await
227    }
228
229    pub fn handle_events_immediately(&mut self) {
230        self.runner.handle_events_immediately()
231    }
232
233    pub fn sync_and_update(&mut self) {
234        let accessibility_update = self
235            .accessibility
236            .process_updates(&mut self.tree.borrow_mut(), &self.events_sender);
237        self.platform
238            .focused_accessibility_id
239            .set(accessibility_update.focus);
240
241        while let Ok(Some(events_chunk)) = self.events_receiver.try_next() {
242            match events_chunk {
243                EventsChunk::Processed(processed_events) => {
244                    let events_executor_adapter = EventsExecutorAdapter {
245                        runner: &mut self.runner,
246                    };
247                    events_executor_adapter.run(&mut self.nodes_state, processed_events);
248                }
249                EventsChunk::Batch(events) => {
250                    for event in events {
251                        self.runner.handle_event(
252                            event.node_id,
253                            event.name,
254                            event.data,
255                            event.bubbles,
256                        );
257                    }
258                }
259            }
260        }
261
262        let mutations = self.runner.sync_and_update();
263        self.tree.borrow_mut().apply_mutations(mutations);
264        self.tree.borrow_mut().measure_layout(
265            self.size,
266            &self.font_collection,
267            &self.font_manager,
268            &self.events_sender,
269            1.0,
270            &self.default_fonts,
271        );
272    }
273
274    /// Poll async tasks and events every `step` time for a total time of `duration`.
275    /// This is useful for animations for instance.
276    pub fn poll(&mut self, step: Duration, duration: Duration) {
277        let started = Instant::now();
278        while started.elapsed() < duration {
279            self.handle_events_immediately();
280            self.sync_and_update();
281            std::thread::sleep(step);
282            self.ticker_sender.broadcast_blocking(()).unwrap();
283        }
284    }
285
286    pub fn send_event(&mut self, platform_event: PlatformEvent) {
287        let mut events_measurer_adapter = EventsMeasurerAdapter {
288            tree: &mut self.tree.borrow_mut(),
289            scale_factor: 1.0,
290        };
291        let processed_events = events_measurer_adapter.run(
292            &mut vec![platform_event],
293            &mut self.nodes_state,
294            self.accessibility.focused_node_id(),
295        );
296        self.events_sender
297            .unbounded_send(EventsChunk::Processed(processed_events))
298            .unwrap();
299    }
300
301    pub fn move_cursor(&mut self, cursor: impl Into<CursorPoint>) {
302        self.send_event(PlatformEvent::Mouse {
303            name: MouseEventName::MouseMove,
304            cursor: cursor.into(),
305            button: Some(MouseButton::Left),
306        })
307    }
308
309    pub fn write_text(&mut self, text: impl ToString) {
310        let text = text.to_string();
311        self.send_event(PlatformEvent::Keyboard {
312            name: KeyboardEventName::KeyDown,
313            key: Key::Character(text),
314            code: Code::Unidentified,
315            modifiers: Modifiers::default(),
316        });
317        self.sync_and_update();
318    }
319
320    pub fn press_key(&mut self, key: Key) {
321        self.send_event(PlatformEvent::Keyboard {
322            name: KeyboardEventName::KeyDown,
323            key,
324            code: Code::Unidentified,
325            modifiers: Modifiers::default(),
326        });
327        self.sync_and_update();
328    }
329
330    pub fn press_cursor(&mut self, cursor: impl Into<CursorPoint>) {
331        let cursor = cursor.into();
332        self.send_event(PlatformEvent::Mouse {
333            name: MouseEventName::MouseDown,
334            cursor,
335            button: Some(MouseButton::Left),
336        });
337        self.sync_and_update();
338    }
339
340    pub fn release_cursor(&mut self, cursor: impl Into<CursorPoint>) {
341        let cursor = cursor.into();
342        self.send_event(PlatformEvent::Mouse {
343            name: MouseEventName::MouseUp,
344            cursor,
345            button: Some(MouseButton::Left),
346        });
347        self.sync_and_update();
348    }
349
350    pub fn click_cursor(&mut self, cursor: impl Into<CursorPoint>) {
351        let cursor = cursor.into();
352        self.send_event(PlatformEvent::Mouse {
353            name: MouseEventName::MouseDown,
354            cursor,
355            button: Some(MouseButton::Left),
356        });
357        self.sync_and_update();
358        self.send_event(PlatformEvent::Mouse {
359            name: MouseEventName::MouseUp,
360            cursor,
361            button: Some(MouseButton::Left),
362        });
363        self.sync_and_update();
364    }
365
366    pub fn scroll(&mut self, cursor: impl Into<CursorPoint>, scroll: impl Into<CursorPoint>) {
367        let cursor = cursor.into();
368        let scroll = scroll.into();
369        self.send_event(PlatformEvent::Wheel {
370            name: WheelEventName::Wheel,
371            scroll,
372            cursor,
373            source: WheelSource::Device,
374        });
375        self.sync_and_update();
376    }
377
378    pub fn render(&mut self) -> SkData {
379        let mut surface = raster_n32_premul((self.size.width as i32, self.size.height as i32))
380            .expect("Failed to create the surface.");
381
382        let render_pipeline = RenderPipeline {
383            font_collection: &mut self.font_collection,
384            font_manager: &self.font_manager,
385            tree: &self.tree.borrow(),
386            canvas: surface.canvas(),
387            scale_factor: 1.0,
388            background: Color::WHITE,
389        };
390        render_pipeline.render();
391
392        let image = surface.image_snapshot();
393        let mut context = surface.direct_context();
394        image
395            .encode(context.as_mut(), EncodedImageFormat::PNG, None)
396            .expect("Failed to encode the snapshot.")
397    }
398
399    pub fn render_to_file(&mut self, path: impl Into<PathBuf>) {
400        let path = path.into();
401
402        let image = self.render();
403
404        let mut snapshot_file = File::create(path).expect("Failed to create the snapshot file.");
405
406        snapshot_file
407            .write_all(&image)
408            .expect("Failed to save the snapshot file.");
409    }
410
411    pub fn find<T>(
412        &self,
413        matcher: impl Fn(TestingNode, &dyn ElementExt) -> Option<T>,
414    ) -> Option<T> {
415        let mut matched = None;
416        {
417            let tree = self.tree.borrow();
418            tree.traverse_depth(|id| {
419                if matched.is_some() {
420                    return;
421                }
422                let element = tree.elements.get(&id).unwrap();
423                let node = TestingNode {
424                    tree: self.tree.clone(),
425                    id,
426                };
427                matched = matcher(node, element.as_ref());
428            });
429        }
430
431        matched
432    }
433
434    pub fn find_many<T>(
435        &self,
436        matcher: impl Fn(TestingNode, &dyn ElementExt) -> Option<T>,
437    ) -> Vec<T> {
438        let mut matched = Vec::new();
439        {
440            let tree = self.tree.borrow();
441            tree.traverse_depth(|id| {
442                let element = tree.elements.get(&id).unwrap();
443                let node = TestingNode {
444                    tree: self.tree.clone(),
445                    id,
446                };
447                if let Some(result) = matcher(node, element.as_ref()) {
448                    matched.push(result);
449                }
450            });
451        }
452
453        matched
454    }
455}
456
457pub struct TestingNode {
458    tree: Rc<RefCell<Tree>>,
459    id: NodeId,
460}
461
462impl TestingNode {
463    pub fn layout(&self) -> LayoutNode {
464        self.tree.borrow().layout.get(&self.id).cloned().unwrap()
465    }
466
467    pub fn children(&self) -> Vec<Self> {
468        let children = self
469            .tree
470            .borrow()
471            .children
472            .get(&self.id)
473            .cloned()
474            .unwrap_or_default();
475
476        children
477            .into_iter()
478            .map(|child_id| Self {
479                id: child_id,
480                tree: self.tree.clone(),
481            })
482            .collect()
483    }
484
485    pub fn is_visible(&self) -> bool {
486        let layout = self.layout();
487        let effect_state = self
488            .tree
489            .borrow()
490            .effect_state
491            .get(&self.id)
492            .cloned()
493            .unwrap();
494
495        effect_state.is_visible(&self.tree.borrow().layout, &layout.area)
496    }
497
498    pub fn element(&self) -> Rc<dyn ElementExt> {
499        self.tree
500            .borrow()
501            .elements
502            .get(&self.id)
503            .cloned()
504            .expect("Element does not exist.")
505    }
506}