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