From a0a2f3aa52ce4d837886652f445be64333a3d5fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 20 Sep 2025 13:51:20 +0200 Subject: [PATCH] Write documentation for `iced_tester` --- tester/src/lib.rs | 89 ++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/tester/src/lib.rs b/tester/src/lib.rs index 724aa28f..2edf44bb 100644 --- a/tester/src/lib.rs +++ b/tester/src/lib.rs @@ -1,5 +1,4 @@ //! Record, edit, and run end-to-end tests for your iced applications. -#![allow(missing_docs)] pub use iced_test as test; pub use iced_test::core; pub use iced_test::program; @@ -39,7 +38,7 @@ pub fn attach(program: P) -> Attach

{ /// A [`Program`] with a [`Tester`] attached to it. #[derive(Debug)] pub struct Attach

{ - /// The original [`Program`] attatched to the [`Tester`]. + /// The original [`Program`] attached to the [`Tester`]. pub program: P, } @@ -48,7 +47,7 @@ where P: Program + 'static, { type State = Tester

; - type Message = Tick

; + type Message = Message

; type Theme = Theme; type Renderer = P::Renderer; type Executor = P::Executor; @@ -79,7 +78,7 @@ where state: &mut Self::State, message: Self::Message, ) -> Task { - state.tick(&self.program, message) + state.tick(&self.program, message.0).map(Message) } fn view<'a>( @@ -87,10 +86,13 @@ where state: &'a Self::State, window: window::Id, ) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> { - state.view(&self.program, window) + state.view(&self.program, window).map(Message) } } +/// A tester decorates a [`Program`] definition and attaches a test recorder on top. +/// +/// It can be used to both record and play [`Ice`] tests. pub struct Tester { viewport: Size, mode: emulator::Mode, @@ -127,8 +129,11 @@ enum Outcome { Success, } +/// The message of a [`Tester`]. +pub struct Message(Tick

); + #[derive(Debug, Clone)] -pub enum Message { +enum Event { ChangeViewport(Size), ModeSelected(emulator::Mode), PresetSelected(String), @@ -143,8 +148,8 @@ pub enum Message { Confirm, } -pub enum Tick { - Tester(Message), +enum Tick { + Tester(Event), Program(P::Message), Emulator(emulator::Event

), Record(instruction::Interaction), @@ -152,7 +157,7 @@ pub enum Tick { } impl Tester

{ - pub fn new(program: &P) -> Self { + fn new(program: &P) -> Self { let (state, _) = program.boot(); let window = program.window().unwrap_or_default(); @@ -174,7 +179,7 @@ impl Tester

{ } } - pub fn is_busy(&self) -> bool { + fn is_busy(&self) -> bool { matches!( self.state, State::Recording { .. } @@ -185,24 +190,24 @@ impl Tester

{ ) } - pub fn update(&mut self, program: &P, message: Message) -> Task> { - match message { - Message::ChangeViewport(viewport) => { + fn update(&mut self, program: &P, event: Event) -> Task> { + match event { + Event::ChangeViewport(viewport) => { self.viewport = viewport; Task::none() } - Message::ModeSelected(mode) => { + Event::ModeSelected(mode) => { self.mode = mode; Task::none() } - Message::PresetSelected(preset) => { + Event::PresetSelected(preset) => { self.preset = Some(preset); Task::none() } - Message::Record => { + Event::Record => { self.edit = None; self.instructions.clear(); @@ -220,7 +225,7 @@ impl Tester

{ Task::run(receiver, Tick::Emulator) } - Message::Stop => { + Event::Stop => { let State::Recording { emulator } = std::mem::replace(&mut self.state, State::Empty) else { @@ -246,7 +251,7 @@ impl Tester

{ Task::none() } - Message::Play => { + Event::Play => { self.confirm(); let (sender, receiver) = mpsc::channel(1); @@ -267,7 +272,7 @@ impl Tester

{ Task::run(receiver, Tick::Emulator) } - Message::Import => { + Event::Import => { use std::fs; let import = rfd::AsyncFileDialog::new() @@ -283,10 +288,10 @@ impl Tester

{ )); }) }) - .map(Message::Imported) + .map(Event::Imported) .map(Tick::Tester) } - Message::Export => { + Event::Export => { use std::fs; use std::thread; @@ -314,7 +319,7 @@ impl Tester

{ }) .discard() } - Message::Imported(Ok(ice)) => { + Event::Imported(Ok(ice)) => { self.viewport = ice.viewport; self.mode = ice.mode; self.preset = ice.preset; @@ -330,7 +335,7 @@ impl Tester

{ Task::none() } - Message::Edit => { + Event::Edit => { if self.is_busy() { return Task::none(); } @@ -346,19 +351,19 @@ impl Tester

{ Task::none() } - Message::Edited(action) => { + Event::Edited(action) => { if let Some(edit) = &mut self.edit { edit.perform(action); } Task::none() } - Message::Confirm => { + Event::Confirm => { self.confirm(); Task::none() } - Message::Imported(Err(error)) => { + Event::Imported(Err(error)) => { log::error!("{error}"); Task::none() @@ -392,7 +397,7 @@ impl Tester

{ }) } - pub fn tick(&mut self, program: &P, tick: Tick

) -> Task> { + fn tick(&mut self, program: &P, tick: Tick

) -> Task> { match tick { Tick::Tester(message) => self.update(program, message), Tick::Program(message) => { @@ -512,7 +517,7 @@ impl Tester

{ } } - pub fn view<'a>( + fn view<'a>( &'a self, program: &P, window: window::Id, @@ -641,18 +646,18 @@ impl Tester

{ .into() } - pub fn controls(&self) -> Element<'_, Message, Theme, P::Renderer> { + fn controls(&self) -> Element<'_, Event, Theme, P::Renderer> { let viewport = row![ text_input("Width", &self.viewport.width.to_string()) .size(14) - .on_input(|width| Message::ChangeViewport(Size { + .on_input(|width| Event::ChangeViewport(Size { width: width.parse().unwrap_or(self.viewport.width), ..self.viewport })), text("x").size(14).font(Font::MONOSPACE), text_input("Height", &self.viewport.height.to_string()) .size(14) - .on_input(|height| Message::ChangeViewport(Size { + .on_input(|height| Event::ChangeViewport(Size { height: height.parse().unwrap_or(self.viewport.height), ..self.viewport })), @@ -664,7 +669,7 @@ impl Tester

{ &self.presets, "Default", self.preset.as_ref(), - Message::PresetSelected, + Event::PresetSelected, ) .size(14) .width(Fill); @@ -672,7 +677,7 @@ impl Tester

{ let mode = pick_list( emulator::Mode::ALL, Some(self.mode), - Message::ModeSelected, + Event::ModeSelected, ) .text_size(14) .width(Fill); @@ -683,7 +688,7 @@ impl Tester

{ .size(12) .height(Fill) .font(Font::MONOSPACE) - .on_action(Message::Edited) + .on_action(Event::Edited) .into() } else if self.instructions.is_empty() { Element::from(center( @@ -761,30 +766,28 @@ impl Tester

{ let play = control(icon::play()).on_press_maybe( (!matches!(self.state, State::Recording { .. }) && !self.instructions.is_empty()) - .then_some(Message::Play), + .then_some(Event::Play), ); let record = if let State::Recording { .. } = &self.state { control(icon::stop()) - .on_press(Message::Stop) + .on_press(Event::Stop) .style(button::success) } else { control(icon::record()) - .on_press_maybe( - (!self.is_busy()).then_some(Message::Record), - ) + .on_press_maybe((!self.is_busy()).then_some(Event::Record)) .style(button::danger) }; let import = control(icon::folder()) - .on_press_maybe((!self.is_busy()).then_some(Message::Import)) + .on_press_maybe((!self.is_busy()).then_some(Event::Import)) .style(button::secondary); let export = control(icon::floppy()) .on_press_maybe( (!matches!(self.state, State::Recording { .. }) && !self.instructions.is_empty()) - .then_some(Message::Export), + .then_some(Event::Export), ) .style(button::success); @@ -799,13 +802,13 @@ impl Tester

{ } else if self.edit.is_none() { button(icon::pencil().size(14)) .padding(0) - .on_press(Message::Edit) + .on_press(Event::Edit) .style(button::text) .into() } else { button(icon::check().size(14)) .padding(0) - .on_press(Message::Confirm) + .on_press(Event::Confirm) .style(button::text) .into() };