Write documentation for new iced_test APIs
This commit is contained in:
parent
5796ba272e
commit
59e2687146
6 changed files with 240 additions and 21 deletions
|
|
@ -1,3 +1,4 @@
|
|||
//! Run your application in a headless runtime.
|
||||
use crate::core;
|
||||
use crate::core::mouse;
|
||||
use crate::core::renderer;
|
||||
|
|
@ -21,6 +22,15 @@ use crate::{Instruction, Selector};
|
|||
|
||||
use std::fmt;
|
||||
|
||||
/// A headless runtime that can run iced applications and execute
|
||||
/// [instructions](crate::Instruction).
|
||||
///
|
||||
/// An [`Emulator`] runs its program as close as possible to the real thing.
|
||||
/// It will run subscriptions and tasks in the [`Executor`](Program::Executor) of
|
||||
/// the [`Program`].
|
||||
///
|
||||
/// If you want to run a simulation without side effects, use a [`Simulator`](crate::Simulator)
|
||||
/// instead.
|
||||
pub struct Emulator<P: Program> {
|
||||
state: P::State,
|
||||
runtime: Runtime<P::Executor, mpsc::Sender<Event<P>>, Event<P>>,
|
||||
|
|
@ -34,18 +44,30 @@ pub struct Emulator<P: Program> {
|
|||
pending_tasks: usize,
|
||||
}
|
||||
|
||||
/// An emulation event.
|
||||
pub enum Event<P: Program> {
|
||||
/// An action that must be [performed](Emulator::perform) by the [`Emulator`].
|
||||
Action(Action<P>),
|
||||
/// An [`Instruction`] failed to be executed.
|
||||
Failed(Instruction),
|
||||
/// The [`Emulator`] is ready.
|
||||
Ready,
|
||||
}
|
||||
|
||||
pub enum Action<P: Program> {
|
||||
/// An action that must be [performed](Emulator::perform) by the [`Emulator`].
|
||||
pub struct Action<P: Program>(Action_<P>);
|
||||
|
||||
enum Action_<P: Program> {
|
||||
Runtime(runtime::Action<P::Message>),
|
||||
CountDown,
|
||||
}
|
||||
|
||||
impl<P: Program + 'static> Emulator<P> {
|
||||
/// Creates a new [`Emulator`] of the [`Program`] with the given [`Mode`] and [`Size`].
|
||||
///
|
||||
/// The [`Emulator`] will send [`Event`] notifications through the provided [`mpsc::Sender`].
|
||||
///
|
||||
/// When the [`Emulator`] has finished booting, an [`Event::Ready`] will be produced.
|
||||
pub fn new(
|
||||
sender: mpsc::Sender<Event<P>>,
|
||||
program: &P,
|
||||
|
|
@ -55,6 +77,10 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
Self::with_preset(sender, program, mode, size, None)
|
||||
}
|
||||
|
||||
/// Creates a new [`Emulator`] analogously to [`new`](Self::new), but it also takes a
|
||||
/// [`program::Preset`] that will be used as the initial state.
|
||||
///
|
||||
/// When the [`Emulator`] has finished booting, an [`Event::Ready`] will be produced.
|
||||
pub fn with_preset(
|
||||
sender: mpsc::Sender<Event<P>>,
|
||||
program: &P,
|
||||
|
|
@ -106,6 +132,11 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
emulator
|
||||
}
|
||||
|
||||
/// Updates the state of the [`Emulator`] program.
|
||||
///
|
||||
/// This is equivalent to calling the [`Program::update`] function,
|
||||
/// resubscribing to any subscriptions, and running the resulting tasks
|
||||
/// concurrently.
|
||||
pub fn update(&mut self, program: &P, message: P::Message) {
|
||||
let task = self
|
||||
.runtime
|
||||
|
|
@ -118,16 +149,24 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
_ => {
|
||||
if let Some(stream) = task::into_stream(task) {
|
||||
self.runtime.run(
|
||||
stream.map(Action::Runtime).map(Event::Action).boxed(),
|
||||
stream
|
||||
.map(Action_::Runtime)
|
||||
.map(Action)
|
||||
.map(Event::Action)
|
||||
.boxed(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs an [`Action`].
|
||||
///
|
||||
/// Whenever an [`Emulator`] sends an [`Event::Action`], this
|
||||
/// method must be called to proceed with the execution.
|
||||
pub fn perform(&mut self, program: &P, action: Action<P>) {
|
||||
match action {
|
||||
Action::CountDown => {
|
||||
match action.0 {
|
||||
Action_::CountDown => {
|
||||
if self.pending_tasks > 0 {
|
||||
self.pending_tasks -= 1;
|
||||
|
||||
|
|
@ -136,7 +175,7 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Action::Runtime(action) => match action {
|
||||
Action_::Runtime(action) => match action {
|
||||
runtime::Action::Output(message) => {
|
||||
self.update(program, message);
|
||||
}
|
||||
|
|
@ -229,6 +268,12 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Runs an [`Instruction`].
|
||||
///
|
||||
/// If the [`Instruction`] executes successfully, an [`Event::Ready`] will be
|
||||
/// produced by the [`Emulator`].
|
||||
///
|
||||
/// Otherwise, an [`Event::Failed`] will be triggered.
|
||||
pub fn run(&mut self, program: &P, instruction: Instruction) {
|
||||
let mut user_interface = UserInterface::build(
|
||||
program.view(&self.state, self.window),
|
||||
|
|
@ -321,7 +366,7 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn wait_for(&mut self, task: Task<P::Message>) {
|
||||
fn wait_for(&mut self, task: Task<P::Message>) {
|
||||
if let Some(stream) = task::into_stream(task) {
|
||||
match self.mode {
|
||||
Mode::Zen => {
|
||||
|
|
@ -329,10 +374,11 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
|
||||
self.runtime.run(
|
||||
stream
|
||||
.map(Action::Runtime)
|
||||
.map(Action_::Runtime)
|
||||
.map(Action)
|
||||
.map(Event::Action)
|
||||
.chain(stream::once(async {
|
||||
Event::Action(Action::CountDown)
|
||||
Event::Action(Action(Action_::CountDown))
|
||||
}))
|
||||
.boxed(),
|
||||
);
|
||||
|
|
@ -340,7 +386,8 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
Mode::Patient => {
|
||||
self.runtime.run(
|
||||
stream
|
||||
.map(Action::Runtime)
|
||||
.map(Action_::Runtime)
|
||||
.map(Action)
|
||||
.map(Event::Action)
|
||||
.chain(stream::once(async { Event::Ready }))
|
||||
.boxed(),
|
||||
|
|
@ -348,7 +395,11 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
}
|
||||
Mode::Impatient => {
|
||||
self.runtime.run(
|
||||
stream.map(Action::Runtime).map(Event::Action).boxed(),
|
||||
stream
|
||||
.map(Action_::Runtime)
|
||||
.map(Action)
|
||||
.map(Event::Action)
|
||||
.boxed(),
|
||||
);
|
||||
self.runtime.send(Event::Ready);
|
||||
}
|
||||
|
|
@ -358,17 +409,18 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn resubscribe(&mut self, program: &P) {
|
||||
fn resubscribe(&mut self, program: &P) {
|
||||
self.runtime
|
||||
.track(subscription::into_recipes(self.runtime.enter(|| {
|
||||
program.subscription(&self.state).map(|message| {
|
||||
Event::Action(Action::Runtime(runtime::Action::Output(
|
||||
message,
|
||||
Event::Action(Action(Action_::Runtime(
|
||||
runtime::Action::Output(message),
|
||||
)))
|
||||
})
|
||||
})));
|
||||
}
|
||||
|
||||
/// Returns the current view of the [`Emulator`].
|
||||
pub fn view(
|
||||
&self,
|
||||
program: &P,
|
||||
|
|
@ -376,24 +428,37 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
program.view(&self.state, self.window)
|
||||
}
|
||||
|
||||
/// Returns the current theme of the [`Emulator`].
|
||||
pub fn theme(&self, program: &P) -> Option<P::Theme> {
|
||||
program.theme(&self.state, self.window)
|
||||
}
|
||||
|
||||
/// Turns the [`Emulator`] into its internal state.
|
||||
pub fn into_state(self) -> (P::State, core::window::Id) {
|
||||
(self.state, self.window)
|
||||
}
|
||||
}
|
||||
|
||||
/// The strategy used by an [`Emulator`] when waiting for tasks to finish.
|
||||
///
|
||||
/// A [`Mode`] can be used to make an [`Emulator`] wait for side effects to finish before
|
||||
/// continuing execution.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum Mode {
|
||||
/// Waits for all tasks spawned by an [`Instruction`], as well as all tasks indirectly
|
||||
/// spawned by the the results of those tasks.
|
||||
///
|
||||
/// This is the default.
|
||||
#[default]
|
||||
Zen,
|
||||
/// Waits only for the tasks directly spawned by an [`Instruction`].
|
||||
Patient,
|
||||
/// Never waits for any tasks to finish.
|
||||
Impatient,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
/// A list of all the available modes.
|
||||
pub const ALL: &[Self] = &[Self::Zen, Self::Patient, Self::Impatient];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,14 @@ use std::sync::Arc;
|
|||
pub enum Error {
|
||||
/// No matching widget was found for the [`Selector`](crate::Selector).
|
||||
#[error("no matching widget was found for the selector: {selector}")]
|
||||
SelectorNotFound { selector: String },
|
||||
SelectorNotFound {
|
||||
/// A description of the selector.
|
||||
selector: String,
|
||||
},
|
||||
/// A target matched, but is not visible.
|
||||
#[error("the matching target is not visible: {target:?}")]
|
||||
TargetNotVisible {
|
||||
/// The target
|
||||
target: Arc<dyn std::fmt::Debug + Send + Sync>,
|
||||
},
|
||||
/// An IO operation failed.
|
||||
|
|
@ -24,21 +29,30 @@ pub enum Error {
|
|||
/// The encoding of some PNG image failed.
|
||||
#[error("the encoding of some PNG image failed: {0}")]
|
||||
PngEncodingFailed(Arc<png::EncodingError>),
|
||||
/// The parsing of an [`Ice`](crate::Ice) test failed.
|
||||
#[error("the ice test ({file}) is invalid: {error}")]
|
||||
IceParsingFailed {
|
||||
/// The path of the test.
|
||||
file: PathBuf,
|
||||
/// The parse error.
|
||||
error: ice::ParseError,
|
||||
},
|
||||
/// The execution of an [`Ice`](crate::Ice) test failed.
|
||||
#[error("the ice test ({file}) failed")]
|
||||
IceTestingFailed {
|
||||
/// The path of the test.
|
||||
file: PathBuf,
|
||||
/// The [`Instruction`] that failed.
|
||||
instruction: Instruction,
|
||||
},
|
||||
/// The [`Preset`](crate::program::Preset) of a program could not be found.
|
||||
#[error(
|
||||
"the preset \"{name}\" does not exist (available presets: {available:?})"
|
||||
)]
|
||||
PresetNotFound {
|
||||
/// The name of the [`Preset`](crate::program::Preset).
|
||||
name: String,
|
||||
/// The available set of presets.
|
||||
available: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,59 @@
|
|||
//! A shareable, simple format of end-to-end tests.
|
||||
use crate::Instruction;
|
||||
use crate::core::Size;
|
||||
use crate::emulator;
|
||||
use crate::instruction;
|
||||
|
||||
/// An end-to-end test for iced applications.
|
||||
///
|
||||
/// Ice tests encode a certain configuration together with a sequence of instructions.
|
||||
/// An ice test passes if all the instructions can be executed successfully.
|
||||
///
|
||||
/// Normally, ice tests are run by an [`Emulator`](crate::Emulator) in continuous
|
||||
/// integration pipelines.
|
||||
///
|
||||
/// Ice tests can be easily run by saving them as `.ice` files in a folder and simply
|
||||
/// calling [`run`](crate::run). These test files can be recorded by enabling the `tester`
|
||||
/// feature flag in the root crate.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Ice {
|
||||
/// The viewport [`Size`] that must be used for the test.
|
||||
pub viewport: Size,
|
||||
/// The [`emulator::Mode`] that must be used for the test.
|
||||
pub mode: emulator::Mode,
|
||||
/// The name of the [`Preset`](crate::program::Preset) that must be used for the test.
|
||||
pub preset: Option<String>,
|
||||
/// The sequence of instructions of the test.
|
||||
pub instructions: Vec<Instruction>,
|
||||
}
|
||||
|
||||
impl Ice {
|
||||
/// Parses an [`Ice`] test from its textual representation.
|
||||
///
|
||||
/// Here is an example of the [`Ice`] test syntax:
|
||||
///
|
||||
/// ```text
|
||||
/// viewport: 500x800
|
||||
/// mode: Impatient
|
||||
/// preset: Empty
|
||||
/// -----
|
||||
/// click at "What needs to be done?"
|
||||
/// type "Create the universe"
|
||||
/// type enter
|
||||
/// type "Make an apple pie"
|
||||
/// type enter
|
||||
/// expect "2 tasks left"
|
||||
/// click at "Create the universe"
|
||||
/// expect "1 task left"
|
||||
/// click at "Make an apple pie"
|
||||
/// expect "0 tasks left"
|
||||
/// ```
|
||||
///
|
||||
/// This syntax is _very_ experimental and extremely likely to change often.
|
||||
/// For this reason, it is reserved for advanced users that want to early test it.
|
||||
///
|
||||
/// Currently, in order to use it, you will need to earn the right and prove you understand
|
||||
/// its experimental nature by reading the code!
|
||||
pub fn parse(content: &str) -> Result<Self, ParseError> {
|
||||
let Some((metadata, rest)) = content.split_once("-") else {
|
||||
return Err(ParseError::NoMetadata);
|
||||
|
|
@ -140,32 +182,64 @@ impl std::fmt::Display for Ice {
|
|||
}
|
||||
}
|
||||
|
||||
/// An error produced during [`Ice::parse`].
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum ParseError {
|
||||
/// No metadata is present.
|
||||
#[error("the ice test has no metadata")]
|
||||
NoMetadata,
|
||||
|
||||
/// The metadata is invalid.
|
||||
#[error("invalid metadata in line {line}: \"{content}\"")]
|
||||
InvalidMetadata { line: usize, content: String },
|
||||
InvalidMetadata {
|
||||
/// The number of the invalid line.
|
||||
line: usize,
|
||||
/// The content of the invalid line.
|
||||
content: String,
|
||||
},
|
||||
|
||||
/// The viewport is invalid.
|
||||
#[error("invalid viewport in line {line}: \"{value}\"")]
|
||||
InvalidViewport { line: usize, value: String },
|
||||
InvalidViewport {
|
||||
/// The number of the invalid line.
|
||||
line: usize,
|
||||
|
||||
/// The invalid value.
|
||||
value: String,
|
||||
},
|
||||
|
||||
/// The [`emulator::Mode`] is invalid.
|
||||
#[error("invalid mode in line {line}: \"{value}\"")]
|
||||
InvalidMode { line: usize, value: String },
|
||||
InvalidMode {
|
||||
/// The number of the invalid line.
|
||||
line: usize,
|
||||
/// The invalid value.
|
||||
value: String,
|
||||
},
|
||||
|
||||
/// A metadata field is unknown.
|
||||
#[error("unknown metadata field in line {line}: \"{field}\"")]
|
||||
UnknownField { line: usize, field: String },
|
||||
UnknownField {
|
||||
/// The number of the invalid line.
|
||||
line: usize,
|
||||
/// The name of the unknown field.
|
||||
field: String,
|
||||
},
|
||||
|
||||
/// Viewport metadata is missing.
|
||||
#[error("metadata is missing the viewport field")]
|
||||
MissingViewport,
|
||||
|
||||
/// [`emulator::Mode`] metadata is missing.
|
||||
#[error("metadata is missing the mode field")]
|
||||
MissingMode,
|
||||
|
||||
/// An [`Instruction`] failed to parse.
|
||||
#[error("invalid instruction in line {line}: {error}")]
|
||||
InvalidInstruction {
|
||||
/// The number of the invalid line.
|
||||
line: usize,
|
||||
/// The parse error.
|
||||
error: instruction::ParseError,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
//! A step in an end-to-end test.
|
||||
use crate::core::keyboard;
|
||||
use crate::core::mouse;
|
||||
use crate::core::{Event, Point};
|
||||
|
|
@ -5,13 +6,19 @@ use crate::simulator;
|
|||
|
||||
use std::fmt;
|
||||
|
||||
/// A step in an end-to-end test.
|
||||
///
|
||||
/// An [`Instruction`] can be run by an [`Emulator`](crate::Emulator).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Instruction {
|
||||
/// A user [`Interaction`].
|
||||
Interact(Interaction),
|
||||
/// A testing [`Expectation`].
|
||||
Expect(Expectation),
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
/// Parses an [`Instruction`] from its textual representation.
|
||||
pub fn parse(line: &str) -> Result<Self, ParseError> {
|
||||
parser::run(line)
|
||||
}
|
||||
|
|
@ -26,13 +33,19 @@ impl fmt::Display for Instruction {
|
|||
}
|
||||
}
|
||||
|
||||
/// A user interaction.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Interaction {
|
||||
/// A mouse interaction.
|
||||
Mouse(Mouse),
|
||||
/// A keyboard interaction.
|
||||
Keyboard(Keyboard),
|
||||
}
|
||||
|
||||
impl Interaction {
|
||||
/// Creates an [`Interaction`] from a runtime [`Event`].
|
||||
///
|
||||
/// This can be useful for recording tests during real usage.
|
||||
pub fn from_event(event: &Event) -> Option<Self> {
|
||||
Some(match event {
|
||||
Event::Mouse(mouse) => Self::Mouse(match mouse {
|
||||
|
|
@ -86,6 +99,17 @@ impl Interaction {
|
|||
})
|
||||
}
|
||||
|
||||
/// Merges two interactions together, if possible.
|
||||
///
|
||||
/// This method can turn certain sequences of interactions into a single one.
|
||||
/// For instance, a mouse movement, left button press, and left button release
|
||||
/// can all be merged into a single click interaction.
|
||||
///
|
||||
/// Merging is lossy and, therefore, it is not always desirable if you are recording
|
||||
/// a test and want full reproducibility.
|
||||
///
|
||||
/// If the interactions cannot be merged, the `next` interaction will be
|
||||
/// returned as the second element of the tuple.
|
||||
pub fn merge(self, next: Self) -> (Self, Option<Self>) {
|
||||
match (self, next) {
|
||||
(Self::Mouse(current), Self::Mouse(next)) => {
|
||||
|
|
@ -185,6 +209,10 @@ impl Interaction {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a list of runtime events representing the [`Interaction`].
|
||||
///
|
||||
/// The `find_target` closure must convert a [`Target`] into its screen
|
||||
/// coordinates.
|
||||
pub fn events(
|
||||
&self,
|
||||
find_target: impl FnOnce(&Target) -> Option<Point>,
|
||||
|
|
@ -256,19 +284,30 @@ impl fmt::Display for Interaction {
|
|||
}
|
||||
}
|
||||
|
||||
/// A mouse interaction.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Mouse {
|
||||
/// The mouse was moved.
|
||||
Move(Target),
|
||||
/// A button was pressed.
|
||||
Press {
|
||||
/// The button.
|
||||
button: mouse::Button,
|
||||
/// The location of the press.
|
||||
at: Option<Target>,
|
||||
},
|
||||
/// A button was released.
|
||||
Release {
|
||||
/// The button.
|
||||
button: mouse::Button,
|
||||
/// The location of the release.
|
||||
at: Option<Target>,
|
||||
},
|
||||
/// A button was clicked.
|
||||
Click {
|
||||
/// The button.
|
||||
button: mouse::Button,
|
||||
/// The location of the click.
|
||||
at: Option<Target>,
|
||||
},
|
||||
}
|
||||
|
|
@ -292,9 +331,12 @@ impl fmt::Display for Mouse {
|
|||
}
|
||||
}
|
||||
|
||||
/// The target of an interaction.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Target {
|
||||
/// A specific point of the viewport.
|
||||
Point(Point),
|
||||
/// A UI element containing the given text.
|
||||
Text(String),
|
||||
}
|
||||
|
||||
|
|
@ -307,11 +349,16 @@ impl fmt::Display for Target {
|
|||
}
|
||||
}
|
||||
|
||||
/// A keyboard interaction.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Keyboard {
|
||||
/// A key was pressed.
|
||||
Press(Key),
|
||||
/// A key was release.
|
||||
Release(Key),
|
||||
/// A key was "typed" (press and released).
|
||||
Type(Key),
|
||||
/// A bunch of text was typed.
|
||||
Typewrite(String),
|
||||
}
|
||||
|
||||
|
|
@ -334,7 +381,11 @@ impl fmt::Display for Keyboard {
|
|||
}
|
||||
}
|
||||
|
||||
/// A keyboard key.
|
||||
///
|
||||
/// Only a small subset of keys is supported currently!
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum Key {
|
||||
Enter,
|
||||
Escape,
|
||||
|
|
@ -399,8 +450,13 @@ mod format {
|
|||
}
|
||||
}
|
||||
|
||||
/// A testing assertion.
|
||||
///
|
||||
/// Expectations are instructions that verify the current state of
|
||||
/// the user interface of an application.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Expectation {
|
||||
/// Expect some element to contain some text.
|
||||
Text(String),
|
||||
}
|
||||
|
||||
|
|
@ -432,6 +488,7 @@ mod parser {
|
|||
use nom::sequence::{delimited, preceded, separated_pair};
|
||||
use nom::{Finish, IResult, Parser};
|
||||
|
||||
/// A parsing error.
|
||||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
#[error("parse error: {0}")]
|
||||
pub struct Error(nom::error::Error<String>);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
//! #
|
||||
//! # let mut counter = Counter { value: 0 };
|
||||
//! # let mut ui = simulator(counter.view());
|
||||
//!
|
||||
//! #
|
||||
//! let _ = ui.click("+");
|
||||
//! let _ = ui.click("+");
|
||||
//! let _ = ui.click("-");
|
||||
|
|
@ -83,7 +83,6 @@
|
|||
//! [`typewrite`](Simulator::typewrite)—and even perform [_snapshot testing_](Simulator::snapshot)!
|
||||
//!
|
||||
//! [the classical counter interface]: https://book.iced.rs/architecture.html#dissecting-an-interface
|
||||
#![allow(missing_docs)]
|
||||
pub use iced_program as program;
|
||||
pub use iced_renderer as renderer;
|
||||
pub use iced_runtime as runtime;
|
||||
|
|
@ -107,6 +106,14 @@ pub use simulator::{Simulator, simulator};
|
|||
|
||||
use std::path::Path;
|
||||
|
||||
/// Runs an [`Ice`] test suite for the given [`Program`](program::Program).
|
||||
///
|
||||
/// Any `.ice` tests will be parsed from the given directory and executed in
|
||||
/// an [`Emulator`] of the given [`Program`](program::Program).
|
||||
///
|
||||
/// Remember that an [`Emulator`] executes the real thing! Side effects _will_
|
||||
/// be performed. It is up to you to ensure your tests have reproducible environments
|
||||
/// by leveraging [`Preset`][program::Preset].
|
||||
pub fn run(
|
||||
program: impl program::Program + 'static,
|
||||
tests_dir: impl AsRef<Path>,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Run a simulation of your application.
|
||||
//! Run a simulation of your application without side effects.
|
||||
use crate::core;
|
||||
use crate::core::clipboard;
|
||||
use crate::core::event;
|
||||
|
|
@ -366,6 +366,7 @@ pub fn click() -> impl Iterator<Item = Event> {
|
|||
.into_iter()
|
||||
}
|
||||
|
||||
/// Returns the sequence of events of a key press.
|
||||
pub fn press_key(
|
||||
key: impl Into<keyboard::Key>,
|
||||
text: Option<SmolStr>,
|
||||
|
|
@ -384,6 +385,7 @@ pub fn press_key(
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns the sequence of events of a key release.
|
||||
pub fn release_key(key: impl Into<keyboard::Key>) -> Event {
|
||||
let key = key.into();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue