Write documentation for iced_tester
This commit is contained in:
parent
c3f90fc420
commit
a0a2f3aa52
1 changed files with 46 additions and 43 deletions
|
|
@ -1,5 +1,4 @@
|
||||||
//! Record, edit, and run end-to-end tests for your iced applications.
|
//! 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 as test;
|
||||||
pub use iced_test::core;
|
pub use iced_test::core;
|
||||||
pub use iced_test::program;
|
pub use iced_test::program;
|
||||||
|
|
@ -39,7 +38,7 @@ pub fn attach<P: Program + 'static>(program: P) -> Attach<P> {
|
||||||
/// A [`Program`] with a [`Tester`] attached to it.
|
/// A [`Program`] with a [`Tester`] attached to it.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Attach<P> {
|
pub struct Attach<P> {
|
||||||
/// The original [`Program`] attatched to the [`Tester`].
|
/// The original [`Program`] attached to the [`Tester`].
|
||||||
pub program: P,
|
pub program: P,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,7 +47,7 @@ where
|
||||||
P: Program + 'static,
|
P: Program + 'static,
|
||||||
{
|
{
|
||||||
type State = Tester<P>;
|
type State = Tester<P>;
|
||||||
type Message = Tick<P>;
|
type Message = Message<P>;
|
||||||
type Theme = Theme;
|
type Theme = Theme;
|
||||||
type Renderer = P::Renderer;
|
type Renderer = P::Renderer;
|
||||||
type Executor = P::Executor;
|
type Executor = P::Executor;
|
||||||
|
|
@ -79,7 +78,7 @@ where
|
||||||
state: &mut Self::State,
|
state: &mut Self::State,
|
||||||
message: Self::Message,
|
message: Self::Message,
|
||||||
) -> Task<Self::Message> {
|
) -> Task<Self::Message> {
|
||||||
state.tick(&self.program, message)
|
state.tick(&self.program, message.0).map(Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view<'a>(
|
fn view<'a>(
|
||||||
|
|
@ -87,10 +86,13 @@ where
|
||||||
state: &'a Self::State,
|
state: &'a Self::State,
|
||||||
window: window::Id,
|
window: window::Id,
|
||||||
) -> Element<'a, Self::Message, Self::Theme, Self::Renderer> {
|
) -> 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<P: Program> {
|
pub struct Tester<P: Program> {
|
||||||
viewport: Size,
|
viewport: Size,
|
||||||
mode: emulator::Mode,
|
mode: emulator::Mode,
|
||||||
|
|
@ -127,8 +129,11 @@ enum Outcome {
|
||||||
Success,
|
Success,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The message of a [`Tester`].
|
||||||
|
pub struct Message<P: Program>(Tick<P>);
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Message {
|
enum Event {
|
||||||
ChangeViewport(Size),
|
ChangeViewport(Size),
|
||||||
ModeSelected(emulator::Mode),
|
ModeSelected(emulator::Mode),
|
||||||
PresetSelected(String),
|
PresetSelected(String),
|
||||||
|
|
@ -143,8 +148,8 @@ pub enum Message {
|
||||||
Confirm,
|
Confirm,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Tick<P: Program> {
|
enum Tick<P: Program> {
|
||||||
Tester(Message),
|
Tester(Event),
|
||||||
Program(P::Message),
|
Program(P::Message),
|
||||||
Emulator(emulator::Event<P>),
|
Emulator(emulator::Event<P>),
|
||||||
Record(instruction::Interaction),
|
Record(instruction::Interaction),
|
||||||
|
|
@ -152,7 +157,7 @@ pub enum Tick<P: Program> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Program + 'static> Tester<P> {
|
impl<P: Program + 'static> Tester<P> {
|
||||||
pub fn new(program: &P) -> Self {
|
fn new(program: &P) -> Self {
|
||||||
let (state, _) = program.boot();
|
let (state, _) = program.boot();
|
||||||
let window = program.window().unwrap_or_default();
|
let window = program.window().unwrap_or_default();
|
||||||
|
|
||||||
|
|
@ -174,7 +179,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_busy(&self) -> bool {
|
fn is_busy(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self.state,
|
self.state,
|
||||||
State::Recording { .. }
|
State::Recording { .. }
|
||||||
|
|
@ -185,24 +190,24 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, program: &P, message: Message) -> Task<Tick<P>> {
|
fn update(&mut self, program: &P, event: Event) -> Task<Tick<P>> {
|
||||||
match message {
|
match event {
|
||||||
Message::ChangeViewport(viewport) => {
|
Event::ChangeViewport(viewport) => {
|
||||||
self.viewport = viewport;
|
self.viewport = viewport;
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::ModeSelected(mode) => {
|
Event::ModeSelected(mode) => {
|
||||||
self.mode = mode;
|
self.mode = mode;
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::PresetSelected(preset) => {
|
Event::PresetSelected(preset) => {
|
||||||
self.preset = Some(preset);
|
self.preset = Some(preset);
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Record => {
|
Event::Record => {
|
||||||
self.edit = None;
|
self.edit = None;
|
||||||
self.instructions.clear();
|
self.instructions.clear();
|
||||||
|
|
||||||
|
|
@ -220,7 +225,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
|
|
||||||
Task::run(receiver, Tick::Emulator)
|
Task::run(receiver, Tick::Emulator)
|
||||||
}
|
}
|
||||||
Message::Stop => {
|
Event::Stop => {
|
||||||
let State::Recording { emulator } =
|
let State::Recording { emulator } =
|
||||||
std::mem::replace(&mut self.state, State::Empty)
|
std::mem::replace(&mut self.state, State::Empty)
|
||||||
else {
|
else {
|
||||||
|
|
@ -246,7 +251,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Play => {
|
Event::Play => {
|
||||||
self.confirm();
|
self.confirm();
|
||||||
|
|
||||||
let (sender, receiver) = mpsc::channel(1);
|
let (sender, receiver) = mpsc::channel(1);
|
||||||
|
|
@ -267,7 +272,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
|
|
||||||
Task::run(receiver, Tick::Emulator)
|
Task::run(receiver, Tick::Emulator)
|
||||||
}
|
}
|
||||||
Message::Import => {
|
Event::Import => {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
let import = rfd::AsyncFileDialog::new()
|
let import = rfd::AsyncFileDialog::new()
|
||||||
|
|
@ -283,10 +288,10 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
));
|
));
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.map(Message::Imported)
|
.map(Event::Imported)
|
||||||
.map(Tick::Tester)
|
.map(Tick::Tester)
|
||||||
}
|
}
|
||||||
Message::Export => {
|
Event::Export => {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
|
|
@ -314,7 +319,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
})
|
})
|
||||||
.discard()
|
.discard()
|
||||||
}
|
}
|
||||||
Message::Imported(Ok(ice)) => {
|
Event::Imported(Ok(ice)) => {
|
||||||
self.viewport = ice.viewport;
|
self.viewport = ice.viewport;
|
||||||
self.mode = ice.mode;
|
self.mode = ice.mode;
|
||||||
self.preset = ice.preset;
|
self.preset = ice.preset;
|
||||||
|
|
@ -330,7 +335,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Edit => {
|
Event::Edit => {
|
||||||
if self.is_busy() {
|
if self.is_busy() {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
|
|
@ -346,19 +351,19 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Edited(action) => {
|
Event::Edited(action) => {
|
||||||
if let Some(edit) = &mut self.edit {
|
if let Some(edit) = &mut self.edit {
|
||||||
edit.perform(action);
|
edit.perform(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Confirm => {
|
Event::Confirm => {
|
||||||
self.confirm();
|
self.confirm();
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::Imported(Err(error)) => {
|
Event::Imported(Err(error)) => {
|
||||||
log::error!("{error}");
|
log::error!("{error}");
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
@ -392,7 +397,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self, program: &P, tick: Tick<P>) -> Task<Tick<P>> {
|
fn tick(&mut self, program: &P, tick: Tick<P>) -> Task<Tick<P>> {
|
||||||
match tick {
|
match tick {
|
||||||
Tick::Tester(message) => self.update(program, message),
|
Tick::Tester(message) => self.update(program, message),
|
||||||
Tick::Program(message) => {
|
Tick::Program(message) => {
|
||||||
|
|
@ -512,7 +517,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view<'a>(
|
fn view<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
program: &P,
|
program: &P,
|
||||||
window: window::Id,
|
window: window::Id,
|
||||||
|
|
@ -641,18 +646,18 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn controls(&self) -> Element<'_, Message, Theme, P::Renderer> {
|
fn controls(&self) -> Element<'_, Event, Theme, P::Renderer> {
|
||||||
let viewport = row![
|
let viewport = row![
|
||||||
text_input("Width", &self.viewport.width.to_string())
|
text_input("Width", &self.viewport.width.to_string())
|
||||||
.size(14)
|
.size(14)
|
||||||
.on_input(|width| Message::ChangeViewport(Size {
|
.on_input(|width| Event::ChangeViewport(Size {
|
||||||
width: width.parse().unwrap_or(self.viewport.width),
|
width: width.parse().unwrap_or(self.viewport.width),
|
||||||
..self.viewport
|
..self.viewport
|
||||||
})),
|
})),
|
||||||
text("x").size(14).font(Font::MONOSPACE),
|
text("x").size(14).font(Font::MONOSPACE),
|
||||||
text_input("Height", &self.viewport.height.to_string())
|
text_input("Height", &self.viewport.height.to_string())
|
||||||
.size(14)
|
.size(14)
|
||||||
.on_input(|height| Message::ChangeViewport(Size {
|
.on_input(|height| Event::ChangeViewport(Size {
|
||||||
height: height.parse().unwrap_or(self.viewport.height),
|
height: height.parse().unwrap_or(self.viewport.height),
|
||||||
..self.viewport
|
..self.viewport
|
||||||
})),
|
})),
|
||||||
|
|
@ -664,7 +669,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
&self.presets,
|
&self.presets,
|
||||||
"Default",
|
"Default",
|
||||||
self.preset.as_ref(),
|
self.preset.as_ref(),
|
||||||
Message::PresetSelected,
|
Event::PresetSelected,
|
||||||
)
|
)
|
||||||
.size(14)
|
.size(14)
|
||||||
.width(Fill);
|
.width(Fill);
|
||||||
|
|
@ -672,7 +677,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
let mode = pick_list(
|
let mode = pick_list(
|
||||||
emulator::Mode::ALL,
|
emulator::Mode::ALL,
|
||||||
Some(self.mode),
|
Some(self.mode),
|
||||||
Message::ModeSelected,
|
Event::ModeSelected,
|
||||||
)
|
)
|
||||||
.text_size(14)
|
.text_size(14)
|
||||||
.width(Fill);
|
.width(Fill);
|
||||||
|
|
@ -683,7 +688,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
.size(12)
|
.size(12)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.font(Font::MONOSPACE)
|
.font(Font::MONOSPACE)
|
||||||
.on_action(Message::Edited)
|
.on_action(Event::Edited)
|
||||||
.into()
|
.into()
|
||||||
} else if self.instructions.is_empty() {
|
} else if self.instructions.is_empty() {
|
||||||
Element::from(center(
|
Element::from(center(
|
||||||
|
|
@ -761,30 +766,28 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
let play = control(icon::play()).on_press_maybe(
|
let play = control(icon::play()).on_press_maybe(
|
||||||
(!matches!(self.state, State::Recording { .. })
|
(!matches!(self.state, State::Recording { .. })
|
||||||
&& !self.instructions.is_empty())
|
&& !self.instructions.is_empty())
|
||||||
.then_some(Message::Play),
|
.then_some(Event::Play),
|
||||||
);
|
);
|
||||||
|
|
||||||
let record = if let State::Recording { .. } = &self.state {
|
let record = if let State::Recording { .. } = &self.state {
|
||||||
control(icon::stop())
|
control(icon::stop())
|
||||||
.on_press(Message::Stop)
|
.on_press(Event::Stop)
|
||||||
.style(button::success)
|
.style(button::success)
|
||||||
} else {
|
} else {
|
||||||
control(icon::record())
|
control(icon::record())
|
||||||
.on_press_maybe(
|
.on_press_maybe((!self.is_busy()).then_some(Event::Record))
|
||||||
(!self.is_busy()).then_some(Message::Record),
|
|
||||||
)
|
|
||||||
.style(button::danger)
|
.style(button::danger)
|
||||||
};
|
};
|
||||||
|
|
||||||
let import = control(icon::folder())
|
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);
|
.style(button::secondary);
|
||||||
|
|
||||||
let export = control(icon::floppy())
|
let export = control(icon::floppy())
|
||||||
.on_press_maybe(
|
.on_press_maybe(
|
||||||
(!matches!(self.state, State::Recording { .. })
|
(!matches!(self.state, State::Recording { .. })
|
||||||
&& !self.instructions.is_empty())
|
&& !self.instructions.is_empty())
|
||||||
.then_some(Message::Export),
|
.then_some(Event::Export),
|
||||||
)
|
)
|
||||||
.style(button::success);
|
.style(button::success);
|
||||||
|
|
||||||
|
|
@ -799,13 +802,13 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
} else if self.edit.is_none() {
|
} else if self.edit.is_none() {
|
||||||
button(icon::pencil().size(14))
|
button(icon::pencil().size(14))
|
||||||
.padding(0)
|
.padding(0)
|
||||||
.on_press(Message::Edit)
|
.on_press(Event::Edit)
|
||||||
.style(button::text)
|
.style(button::text)
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
button(icon::check().size(14))
|
button(icon::check().size(14))
|
||||||
.padding(0)
|
.padding(0)
|
||||||
.on_press(Message::Confirm)
|
.on_press(Event::Confirm)
|
||||||
.style(button::text)
|
.style(button::text)
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue