diff options
author | 2024-12-03 22:03:06 +0100 | |
---|---|---|
committer | 2024-12-10 04:51:08 +0100 | |
commit | d09d5d45ae4697eef277dfe30756b91c7d802a94 (patch) | |
tree | ce1852b218aa46f5400cee383ccfb68faa61abaf /test | |
parent | d6182299b9db7a1928b7740736d53196e33d66e3 (diff) | |
download | iced-d09d5d45ae4697eef277dfe30756b91c7d802a94.tar.gz iced-d09d5d45ae4697eef277dfe30756b91c7d802a94.tar.bz2 iced-d09d5d45ae4697eef277dfe30756b91c7d802a94.zip |
Draft `iced_test` crate and test `todos` example
Diffstat (limited to 'test')
-rw-r--r-- | test/Cargo.toml | 21 | ||||
-rw-r--r-- | test/src/lib.rs | 296 | ||||
-rw-r--r-- | test/src/selector.rs | 24 |
3 files changed, 341 insertions, 0 deletions
diff --git a/test/Cargo.toml b/test/Cargo.toml new file mode 100644 index 00000000..c09a196d --- /dev/null +++ b/test/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "iced_test" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +categories.workspace = true +keywords.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] +iced_runtime.workspace = true +iced_tiny_skia.workspace = true + +iced_renderer.workspace = true +iced_renderer.features = ["tiny-skia"] diff --git a/test/src/lib.rs b/test/src/lib.rs new file mode 100644 index 00000000..211ed4a2 --- /dev/null +++ b/test/src/lib.rs @@ -0,0 +1,296 @@ +//! Test your `iced` applications in headless mode. +#![allow(missing_docs, missing_debug_implementations)] +pub mod selector; + +pub use selector::Selector; + +use iced_renderer as renderer; +use iced_runtime as runtime; +use iced_runtime::core; +use iced_tiny_skia as tiny_skia; + +use crate::core::clipboard; +use crate::core::keyboard; +use crate::core::mouse; +use crate::core::widget; +use crate::core::{Element, Event, Font, Pixels, Rectangle, Size, SmolStr}; +use crate::renderer::Renderer; +use crate::runtime::user_interface; +use crate::runtime::UserInterface; + +pub fn interface<'a, Message, Theme>( + element: impl Into<Element<'a, Message, Theme, Renderer>>, +) -> Interface<'a, Message, Theme, Renderer> { + let mut renderer = Renderer::Secondary(tiny_skia::Renderer::new( + Font::default(), + Pixels(16.0), + )); + + let raw = UserInterface::build( + element, + Size::new(1024.0, 1024.0), + user_interface::Cache::default(), + &mut renderer, + ); + + Interface { + raw, + renderer, + messages: Vec::new(), + } +} + +pub struct Interface<'a, Message, Theme, Renderer> { + raw: UserInterface<'a, Message, Theme, Renderer>, + renderer: Renderer, + messages: Vec<Message>, +} + +pub struct Target { + bounds: Rectangle, +} + +impl<Message, Theme, Renderer> Interface<'_, Message, Theme, Renderer> +where + Renderer: core::Renderer, +{ + pub fn find( + &mut self, + selector: impl Into<Selector>, + ) -> Result<Target, Error> { + let selector = selector.into(); + + match &selector { + Selector::Id(id) => { + struct FindById<'a> { + id: &'a widget::Id, + target: Option<Target>, + } + + impl widget::Operation for FindById<'_> { + fn container( + &mut self, + id: Option<&widget::Id>, + bounds: Rectangle, + operate_on_children: &mut dyn FnMut( + &mut dyn widget::Operation<()>, + ), + ) { + if self.target.is_some() { + return; + } + + if Some(self.id) == id { + self.target = Some(Target { bounds }); + return; + } + + operate_on_children(self); + } + + fn scrollable( + &mut self, + id: Option<&widget::Id>, + bounds: Rectangle, + _content_bounds: Rectangle, + _translation: core::Vector, + _state: &mut dyn widget::operation::Scrollable, + ) { + if self.target.is_some() { + return; + } + + if Some(self.id) == id { + self.target = Some(Target { bounds }); + } + } + + fn text_input( + &mut self, + id: Option<&widget::Id>, + bounds: Rectangle, + _state: &mut dyn widget::operation::TextInput, + ) { + if self.target.is_some() { + return; + } + + if Some(self.id) == id { + self.target = Some(Target { bounds }); + } + } + + fn text( + &mut self, + id: Option<&widget::Id>, + bounds: Rectangle, + _text: &str, + ) { + if self.target.is_some() { + return; + } + + if Some(self.id) == id { + self.target = Some(Target { bounds }); + } + } + + fn custom( + &mut self, + id: Option<&widget::Id>, + bounds: Rectangle, + _state: &mut dyn std::any::Any, + ) { + if self.target.is_some() { + return; + } + + if Some(self.id) == id { + self.target = Some(Target { bounds }); + } + } + } + + let mut find = FindById { id, target: None }; + + self.raw.operate(&self.renderer, &mut find); + + find.target.ok_or(Error::NotFound(selector)) + } + Selector::Text(text) => { + struct FindByText<'a> { + text: &'a str, + target: Option<Target>, + } + + impl widget::Operation for FindByText<'_> { + fn container( + &mut self, + _id: Option<&widget::Id>, + _bounds: Rectangle, + operate_on_children: &mut dyn FnMut( + &mut dyn widget::Operation<()>, + ), + ) { + if self.target.is_some() { + return; + } + + operate_on_children(self); + } + + fn text( + &mut self, + _id: Option<&widget::Id>, + bounds: Rectangle, + text: &str, + ) { + if self.target.is_some() { + return; + } + + if self.text == text { + self.target = Some(Target { bounds }); + } + } + } + + let mut find = FindByText { text, target: None }; + + self.raw.operate(&self.renderer, &mut find); + + find.target.ok_or(Error::NotFound(selector)) + } + } + } + + pub fn click( + &mut self, + selector: impl Into<Selector>, + ) -> Result<Target, Error> { + let target = self.find(selector)?; + + let _ = self.raw.update( + &[ + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)), + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)), + ], + mouse::Cursor::Available(target.bounds.center()), + &mut self.renderer, + &mut clipboard::Null, + &mut self.messages, + ); + + Ok(target) + } + + pub fn typewrite(&mut self, text: impl AsRef<str>) { + let events: Vec<_> = text + .as_ref() + .chars() + .map(|c| SmolStr::new_inline(&c.to_string())) + .flat_map(|c| { + key_press_and_release( + keyboard::Key::Character(c.clone()), + Some(c), + ) + }) + .collect(); + + let _ = self.raw.update( + &events, + mouse::Cursor::Unavailable, + &mut self.renderer, + &mut clipboard::Null, + &mut self.messages, + ); + } + + pub fn press_key(&mut self, key: impl Into<keyboard::Key>) { + let _ = self.raw.update( + &key_press_and_release(key, None), + mouse::Cursor::Unavailable, + &mut self.renderer, + &mut clipboard::Null, + &mut self.messages, + ); + } + + pub fn into_messages(self) -> impl IntoIterator<Item = Message> { + self.messages + } +} + +fn key_press_and_release( + key: impl Into<keyboard::Key>, + text: Option<SmolStr>, +) -> [Event; 2] { + let key = key.into(); + + [ + Event::Keyboard(keyboard::Event::KeyPressed { + key: key.clone(), + modified_key: key.clone(), + physical_key: keyboard::key::Physical::Unidentified( + keyboard::key::NativeCode::Unidentified, + ), + location: keyboard::Location::Standard, + modifiers: keyboard::Modifiers::default(), + text, + }), + Event::Keyboard(keyboard::Event::KeyReleased { + key: key.clone(), + modified_key: key, + physical_key: keyboard::key::Physical::Unidentified( + keyboard::key::NativeCode::Unidentified, + ), + location: keyboard::Location::Standard, + modifiers: keyboard::Modifiers::default(), + }), + ] +} + +#[derive(Debug, Clone)] +pub enum Error { + NotFound(Selector), +} diff --git a/test/src/selector.rs b/test/src/selector.rs new file mode 100644 index 00000000..54faa1a9 --- /dev/null +++ b/test/src/selector.rs @@ -0,0 +1,24 @@ +use crate::core::text; +use crate::core::widget; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Selector { + Id(widget::Id), + Text(text::Fragment<'static>), +} + +impl From<widget::Id> for Selector { + fn from(id: widget::Id) -> Self { + Self::Id(id) + } +} + +impl From<&'static str> for Selector { + fn from(id: &'static str) -> Self { + Self::Id(widget::Id::new(id)) + } +} + +pub fn text(fragment: impl text::IntoFragment<'static>) -> Selector { + Selector::Text(fragment.into_fragment()) +} |