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    ticker_sender: RenderingTickerSender,
87
88    default_fonts: Vec<Cow<'static, str>>,
89}
90
91impl TestingRunner {
92    pub fn new<T>(
93        app: impl Into<FpRender>,
94        size: Size2D,
95        hook: impl FnOnce(&mut Runner) -> T,
96    ) -> (Self, T) {
97        let (events_sender, events_receiver) = futures_channel::mpsc::unbounded();
98        let app = app.into();
99        let mut runner = Runner::new(move || integration(app.clone()).into_element());
100
101        runner.provide_root_context(ScreenReader::new);
102
103        let (mut ticker_sender, ticker) = RenderingTicker::new();
104        ticker_sender.set_overflow(true);
105        runner.provide_root_context(|| ticker);
106
107        let animation_clock = AnimationClock::new();
108        runner.provide_root_context(|| animation_clock.clone());
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 mutations = runner.sync_and_update();
165        tree.borrow_mut().apply_mutations(mutations);
166        tree.borrow_mut().measure_layout(
167            size,
168            &font_collection,
169            &font_manager,
170            &events_sender,
171            1.0,
172            &default_fonts(),
173        );
174
175        let nodes_state = NodesState::default();
176        let accessibility = AccessibilityTree::default();
177
178        (
179            Self {
180                runner,
181                tree,
182                size,
183
184                accessibility,
185                platform,
186
187                nodes_state,
188                events_receiver,
189                events_sender,
190
191                font_manager,
192                font_collection,
193
194                ticker_sender,
195
196                default_fonts: default_fonts(),
197            },
198            hook_result,
199        )
200    }
201
202    pub fn set_fonts(&mut self, fonts: HashMap<&str, &[u8]>) {
203        let mut provider = TypefaceFontProvider::new();
204        for (font_name, font_data) in fonts {
205            let ft_type = self
206                .font_collection
207                .fallback_manager()
208                .unwrap()
209                .new_from_data(font_data, None)
210                .unwrap_or_else(|| panic!("Failed to load font {font_name}."));
211            provider.register_typeface(ft_type, Some(font_name));
212        }
213        let font_manager: FontMgr = provider.into();
214        self.font_manager = font_manager.clone();
215        self.font_collection.set_dynamic_font_manager(font_manager);
216    }
217
218    pub fn set_default_fonts(&mut self, fonts: &[Cow<'static, str>]) {
219        self.default_fonts.clear();
220        self.default_fonts.extend_from_slice(fonts);
221        self.tree.borrow_mut().layout.reset();
222        self.tree.borrow_mut().text_cache.reset();
223        self.tree.borrow_mut().measure_layout(
224            self.size,
225            &self.font_collection,
226            &self.font_manager,
227            &self.events_sender,
228            1.0,
229            &self.default_fonts,
230        );
231        self.tree.borrow_mut().accessibility_diff.clear();
232        self.accessibility.focused_id = ACCESSIBILITY_ROOT_ID;
233        self.accessibility.init(&mut self.tree.borrow_mut());
234        self.sync_and_update();
235    }
236
237    pub async fn handle_events(&mut self) {
238        self.runner.handle_events().await
239    }
240
241    pub fn handle_events_immediately(&mut self) {
242        self.runner.handle_events_immediately()
243    }
244
245    pub fn sync_and_update(&mut self) {
246        let accessibility_update = self
247            .accessibility
248            .process_updates(&mut self.tree.borrow_mut(), &self.events_sender);
249        self.platform
250            .focused_accessibility_id
251            .set(accessibility_update.focus);
252
253        while let Ok(Some(events_chunk)) = self.events_receiver.try_next() {
254            match events_chunk {
255                EventsChunk::Processed(processed_events) => {
256                    let events_executor_adapter = EventsExecutorAdapter {
257                        runner: &mut self.runner,
258                    };
259                    events_executor_adapter.run(&mut self.nodes_state, processed_events);
260                }
261                EventsChunk::Batch(events) => {
262                    for event in events {
263                        self.runner.handle_event(
264                            event.node_id,
265                            event.name,
266                            event.data,
267                            event.bubbles,
268                        );
269                    }
270                }
271            }
272        }
273
274        let mutations = self.runner.sync_and_update();
275        self.tree.borrow_mut().apply_mutations(mutations);
276        self.tree.borrow_mut().measure_layout(
277            self.size,
278            &self.font_collection,
279            &self.font_manager,
280            &self.events_sender,
281            1.0,
282            &self.default_fonts,
283        );
284    }
285
286    /// Poll async tasks and events every `step` time for a total time of `duration`.
287    /// This is useful for animations for instance.
288    pub fn poll(&mut self, step: Duration, duration: Duration) {
289        let started = Instant::now();
290        while started.elapsed() < duration {
291            self.handle_events_immediately();
292            self.sync_and_update();
293            std::thread::sleep(step);
294            self.ticker_sender.broadcast_blocking(()).unwrap();
295        }
296    }
297
298    pub fn send_event(&mut self, platform_event: PlatformEvent) {
299        let mut events_measurer_adapter = EventsMeasurerAdapter {
300            tree: &mut self.tree.borrow_mut(),
301            scale_factor: 1.0,
302        };
303        let processed_events = events_measurer_adapter.run(
304            &mut vec![platform_event],
305            &mut self.nodes_state,
306            self.accessibility.focused_node_id(),
307        );
308        self.events_sender
309            .unbounded_send(EventsChunk::Processed(processed_events))
310            .unwrap();
311    }
312
313    pub fn move_cursor(&mut self, cursor: impl Into<CursorPoint>) {
314        self.send_event(PlatformEvent::Mouse {
315            name: MouseEventName::MouseMove,
316            cursor: cursor.into(),
317            button: Some(MouseButton::Left),
318        })
319    }
320
321    pub fn write_text(&mut self, text: impl ToString) {
322        let text = text.to_string();
323        self.send_event(PlatformEvent::Keyboard {
324            name: KeyboardEventName::KeyDown,
325            key: Key::Character(text),
326            code: Code::Unidentified,
327            modifiers: Modifiers::default(),
328        });
329        self.sync_and_update();
330    }
331
332    pub fn press_key(&mut self, key: Key) {
333        self.send_event(PlatformEvent::Keyboard {
334            name: KeyboardEventName::KeyDown,
335            key,
336            code: Code::Unidentified,
337            modifiers: Modifiers::default(),
338        });
339        self.sync_and_update();
340    }
341
342    pub fn press_cursor(&mut self, cursor: impl Into<CursorPoint>) {
343        let cursor = cursor.into();
344        self.send_event(PlatformEvent::Mouse {
345            name: MouseEventName::MouseDown,
346            cursor,
347            button: Some(MouseButton::Left),
348        });
349        self.sync_and_update();
350    }
351
352    pub fn release_cursor(&mut self, cursor: impl Into<CursorPoint>) {
353        let cursor = cursor.into();
354        self.send_event(PlatformEvent::Mouse {
355            name: MouseEventName::MouseUp,
356            cursor,
357            button: Some(MouseButton::Left),
358        });
359        self.sync_and_update();
360    }
361
362    pub fn click_cursor(&mut self, cursor: impl Into<CursorPoint>) {
363        let cursor = cursor.into();
364        self.send_event(PlatformEvent::Mouse {
365            name: MouseEventName::MouseDown,
366            cursor,
367            button: Some(MouseButton::Left),
368        });
369        self.sync_and_update();
370        self.send_event(PlatformEvent::Mouse {
371            name: MouseEventName::MouseUp,
372            cursor,
373            button: Some(MouseButton::Left),
374        });
375        self.sync_and_update();
376    }
377
378    pub fn scroll(&mut self, cursor: impl Into<CursorPoint>, scroll: impl Into<CursorPoint>) {
379        let cursor = cursor.into();
380        let scroll = scroll.into();
381        self.send_event(PlatformEvent::Wheel {
382            name: WheelEventName::Wheel,
383            scroll,
384            cursor,
385            source: WheelSource::Device,
386        });
387        self.sync_and_update();
388    }
389
390    pub fn render(&mut self) -> SkData {
391        let mut surface = raster_n32_premul((self.size.width as i32, self.size.height as i32))
392            .expect("Failed to create the surface.");
393
394        let render_pipeline = RenderPipeline {
395            font_collection: &mut self.font_collection,
396            font_manager: &self.font_manager,
397            tree: &self.tree.borrow(),
398            canvas: surface.canvas(),
399            scale_factor: 1.0,
400            background: Color::WHITE,
401        };
402        render_pipeline.render();
403
404        let image = surface.image_snapshot();
405        let mut context = surface.direct_context();
406        image
407            .encode(context.as_mut(), EncodedImageFormat::PNG, None)
408            .expect("Failed to encode the snapshot.")
409    }
410
411    pub fn render_to_file(&mut self, path: impl Into<PathBuf>) {
412        let path = path.into();
413
414        let image = self.render();
415
416        let mut snapshot_file = File::create(path).expect("Failed to create the snapshot file.");
417
418        snapshot_file
419            .write_all(&image)
420            .expect("Failed to save the snapshot file.");
421    }
422
423    pub fn find<T>(
424        &self,
425        matcher: impl Fn(TestingNode, &dyn ElementExt) -> Option<T>,
426    ) -> Option<T> {
427        let mut matched = None;
428        {
429            let tree = self.tree.borrow();
430            tree.traverse_depth(|id| {
431                if matched.is_some() {
432                    return;
433                }
434                let element = tree.elements.get(&id).unwrap();
435                let node = TestingNode {
436                    tree: self.tree.clone(),
437                    id,
438                };
439                matched = matcher(node, element.as_ref());
440            });
441        }
442
443        matched
444    }
445
446    pub fn find_many<T>(
447        &self,
448        matcher: impl Fn(TestingNode, &dyn ElementExt) -> Option<T>,
449    ) -> Vec<T> {
450        let mut matched = Vec::new();
451        {
452            let tree = self.tree.borrow();
453            tree.traverse_depth(|id| {
454                let element = tree.elements.get(&id).unwrap();
455                let node = TestingNode {
456                    tree: self.tree.clone(),
457                    id,
458                };
459                if let Some(result) = matcher(node, element.as_ref()) {
460                    matched.push(result);
461                }
462            });
463        }
464
465        matched
466    }
467}
468
469pub struct TestingNode {
470    tree: Rc<RefCell<Tree>>,
471    id: NodeId,
472}
473
474impl TestingNode {
475    pub fn layout(&self) -> LayoutNode {
476        self.tree.borrow().layout.get(&self.id).cloned().unwrap()
477    }
478
479    pub fn children(&self) -> Vec<Self> {
480        let children = self
481            .tree
482            .borrow()
483            .children
484            .get(&self.id)
485            .cloned()
486            .unwrap_or_default();
487
488        children
489            .into_iter()
490            .map(|child_id| Self {
491                id: child_id,
492                tree: self.tree.clone(),
493            })
494            .collect()
495    }
496
497    pub fn is_visible(&self) -> bool {
498        let layout = self.layout();
499        let effect_state = self
500            .tree
501            .borrow()
502            .effect_state
503            .get(&self.id)
504            .cloned()
505            .unwrap();
506
507        effect_state.is_visible(&self.tree.borrow().layout, &layout.area)
508    }
509
510    pub fn element(&self) -> Rc<dyn ElementExt> {
511        self.tree
512            .borrow()
513            .elements
514            .get(&self.id)
515            .cloned()
516            .expect("Element does not exist.")
517    }
518}