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