Write documentation for iced_test
This commit is contained in:
parent
8f6f5c13b0
commit
5220a064c5
7 changed files with 189 additions and 13 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1064,6 +1064,7 @@ name = "counter"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iced",
|
"iced",
|
||||||
|
"iced_test",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2517,6 +2518,7 @@ dependencies = [
|
||||||
"iced_runtime",
|
"iced_runtime",
|
||||||
"png",
|
"png",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,6 @@ iced.workspace = true
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
iced.workspace = true
|
iced.workspace = true
|
||||||
iced.features = ["webgl", "fira-sans"]
|
iced.features = ["webgl", "fira-sans"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
iced_test.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -38,3 +38,32 @@ impl Counter {
|
||||||
.align_x(Center)
|
.align_x(Center)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use iced_test::selector::text;
|
||||||
|
use iced_test::{simulator, Error};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_counts() -> Result<(), Error> {
|
||||||
|
let mut counter = Counter { value: 0 };
|
||||||
|
let mut ui = simulator(counter.view());
|
||||||
|
|
||||||
|
let _ = ui.click(text("Increment"));
|
||||||
|
let _ = ui.click(text("Increment"));
|
||||||
|
let _ = ui.click(text("Decrement"));
|
||||||
|
|
||||||
|
for message in ui.into_messages() {
|
||||||
|
counter.update(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(counter.value, 1);
|
||||||
|
|
||||||
|
let mut ui = simulator(counter.view());
|
||||||
|
|
||||||
|
assert!(ui.find(text("1")).is_ok(), "Counter should display 1!");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -591,7 +591,8 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use iced::{Settings, Theme};
|
use iced::{Settings, Theme};
|
||||||
use iced_test::{selector, Error, Simulator};
|
use iced_test::selector::text;
|
||||||
|
use iced_test::{Error, Simulator};
|
||||||
|
|
||||||
fn simulator(todos: &Todos) -> Simulator<Message> {
|
fn simulator(todos: &Todos) -> Simulator<Message> {
|
||||||
Simulator::with_settings(
|
Simulator::with_settings(
|
||||||
|
|
@ -619,7 +620,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ui = simulator(&todos);
|
let mut ui = simulator(&todos);
|
||||||
let _ = ui.find(selector::text("Create the universe"))?;
|
let _ = ui.find(text("Create the universe"))?;
|
||||||
|
|
||||||
let snapshot = ui.snapshot(&Theme::Dark)?;
|
let snapshot = ui.snapshot(&Theme::Dark)?;
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
||||||
|
|
@ -21,3 +21,4 @@ iced_renderer.features = ["fira-sans"]
|
||||||
|
|
||||||
png.workspace = true
|
png.workspace = true
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|
|
||||||
157
test/src/lib.rs
157
test/src/lib.rs
|
|
@ -1,5 +1,91 @@
|
||||||
//! Test your `iced` applications in headless mode.
|
//! Test your `iced` applications in headless mode.
|
||||||
#![allow(missing_docs, missing_debug_implementations)]
|
//!
|
||||||
|
//! # Basic Usage
|
||||||
|
//! Let's assume we want to test [the classical counter interface].
|
||||||
|
//!
|
||||||
|
//! First, we will want to create a [`Simulator`] of our interface:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! # struct Counter { value: i64 }
|
||||||
|
//! # impl Counter {
|
||||||
|
//! # pub fn view(&self) -> iced_runtime::core::Element<(), iced_runtime::core::Theme, iced_renderer::Renderer> { unimplemented!() }
|
||||||
|
//! # }
|
||||||
|
//! use iced_test::simulator;
|
||||||
|
//!
|
||||||
|
//! let mut counter = Counter { value: 0 };
|
||||||
|
//! let mut ui = simulator(counter.view());
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Now we can simulate a user interacting with our interface. Let's use [`Simulator::click`] to click
|
||||||
|
//! the counter buttons:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! # struct Counter { value: i64 }
|
||||||
|
//! # impl Counter {
|
||||||
|
//! # pub fn view(&self) -> iced_runtime::core::Element<(), iced_runtime::core::Theme, iced_renderer::Renderer> { unimplemented!() }
|
||||||
|
//! # }
|
||||||
|
//! use iced_test::selector::text;
|
||||||
|
//! # use iced_test::simulator;
|
||||||
|
//! #
|
||||||
|
//! # let mut counter = Counter { value: 0 };
|
||||||
|
//! # let mut ui = simulator(counter.view());
|
||||||
|
//!
|
||||||
|
//! let _ = ui.click(text("+"));
|
||||||
|
//! let _ = ui.click(text("+"));
|
||||||
|
//! let _ = ui.click(text("-"));
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! [`Simulator::click`] takes a [`Selector`]. A [`Selector`] describes a way to query the widgets of an interface. In this case,
|
||||||
|
//! [`selector::text`] lets us select a widget by the text it contains.
|
||||||
|
//!
|
||||||
|
//! We can now process any messages produced by these interactions, and then make sure that the final value of our counter is
|
||||||
|
//! indeed `1`!
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! # struct Counter { value: i64 }
|
||||||
|
//! # impl Counter {
|
||||||
|
//! # pub fn update(&mut self, message: ()) {}
|
||||||
|
//! # pub fn view(&self) -> iced_runtime::core::Element<(), iced_runtime::core::Theme, iced_renderer::Renderer> { unimplemented!() }
|
||||||
|
//! # }
|
||||||
|
//! # use iced_test::selector::text;
|
||||||
|
//! # use iced_test::simulator;
|
||||||
|
//! #
|
||||||
|
//! # let mut counter = Counter { value: 0 };
|
||||||
|
//! # let mut ui = simulator(counter.view());
|
||||||
|
//! #
|
||||||
|
//! # let _ = ui.click(text("+"));
|
||||||
|
//! # let _ = ui.click(text("+"));
|
||||||
|
//! # let _ = ui.click(text("-"));
|
||||||
|
//! #
|
||||||
|
//! for message in ui.into_messages() {
|
||||||
|
//! counter.update(message);
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! assert_eq!(counter.value, 1);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! We can even rebuild the interface to make sure the counter _displays_ the proper value with [`Simulator::find`]:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! # struct Counter { value: i64 }
|
||||||
|
//! # impl Counter {
|
||||||
|
//! # pub fn view(&self) -> iced_runtime::core::Element<(), iced_runtime::core::Theme, iced_renderer::Renderer> { unimplemented!() }
|
||||||
|
//! # }
|
||||||
|
//! # use iced_test::selector::text;
|
||||||
|
//! # use iced_test::simulator;
|
||||||
|
//! #
|
||||||
|
//! # let mut counter = Counter { value: 0 };
|
||||||
|
//! let mut ui = simulator(counter.view());
|
||||||
|
//!
|
||||||
|
//! assert!(ui.find(text("1")).is_ok(), "Counter should display 1!");
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! And that's it! That's the gist of testing `iced` applications!
|
||||||
|
//!
|
||||||
|
//! [`Simulator`] contains additional operations you can use to simulate more interactions—like [`tap_key`](Simulator::tap_key) or
|
||||||
|
//! [`typewrite`](Simulator::typewrite)—and even perform [_snapshot testing_](Simulator::snapshot)!
|
||||||
|
//!
|
||||||
|
//! [the classical counter interface]: https://book.iced.rs/architecture.html#dissecting-an-interface
|
||||||
pub mod selector;
|
pub mod selector;
|
||||||
|
|
||||||
pub use selector::Selector;
|
pub use selector::Selector;
|
||||||
|
|
@ -28,6 +114,9 @@ use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Creates a new [`Simulator`].
|
||||||
|
///
|
||||||
|
/// This is just a function version of [`Simulator::new`].
|
||||||
pub fn simulator<'a, Message, Theme, Renderer>(
|
pub fn simulator<'a, Message, Theme, Renderer>(
|
||||||
element: impl Into<Element<'a, Message, Theme, Renderer>>,
|
element: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
) -> Simulator<'a, Message, Theme, Renderer>
|
) -> Simulator<'a, Message, Theme, Renderer>
|
||||||
|
|
@ -38,15 +127,8 @@ where
|
||||||
Simulator::new(element)
|
Simulator::new(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_font(font: impl Into<Cow<'static, [u8]>>) -> Result<(), Error> {
|
/// A user interface that can be interacted with and inspected programmatically.
|
||||||
renderer::graphics::text::font_system()
|
#[allow(missing_debug_implementations)]
|
||||||
.write()
|
|
||||||
.expect("Write to font system")
|
|
||||||
.load_font(font.into());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Simulator<
|
pub struct Simulator<
|
||||||
'a,
|
'a,
|
||||||
Message,
|
Message,
|
||||||
|
|
@ -60,7 +142,10 @@ pub struct Simulator<
|
||||||
messages: Vec<Message>,
|
messages: Vec<Message>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A specific area of a [`Simulator`], normally containing a widget.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Target {
|
pub struct Target {
|
||||||
|
/// The bounds of the area.
|
||||||
pub bounds: Rectangle,
|
pub bounds: Rectangle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,12 +154,14 @@ where
|
||||||
Theme: theme::Base,
|
Theme: theme::Base,
|
||||||
Renderer: core::Renderer + core::renderer::Headless,
|
Renderer: core::Renderer + core::renderer::Headless,
|
||||||
{
|
{
|
||||||
|
/// Creates a new [`Simulator`] with default [`Settings`] and a default size (1024x768).
|
||||||
pub fn new(
|
pub fn new(
|
||||||
element: impl Into<Element<'a, Message, Theme, Renderer>>,
|
element: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::with_settings(Settings::default(), element)
|
Self::with_settings(Settings::default(), element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Simulator`] with the given [`Settings`] and a default size (1024x768).
|
||||||
pub fn with_settings(
|
pub fn with_settings(
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
element: impl Into<Element<'a, Message, Theme, Renderer>>,
|
element: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
|
|
@ -82,6 +169,7 @@ where
|
||||||
Self::with_size(settings, window::Settings::default().size, element)
|
Self::with_size(settings, window::Settings::default().size, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Simulator`] with the given [`Settings`] and size.
|
||||||
pub fn with_size(
|
pub fn with_size(
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
size: impl Into<Size>,
|
size: impl Into<Size>,
|
||||||
|
|
@ -117,6 +205,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finds the [`Target`] of the given widget [`Selector`] in the [`Simulator`].
|
||||||
pub fn find(
|
pub fn find(
|
||||||
&mut self,
|
&mut self,
|
||||||
selector: impl Into<Selector>,
|
selector: impl Into<Selector>,
|
||||||
|
|
@ -265,10 +354,18 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Points the mouse cursor at the given position in the [`Simulator`].
|
||||||
|
///
|
||||||
|
/// This does _not_ produce mouse movement events!
|
||||||
pub fn point_at(&mut self, position: impl Into<Point>) {
|
pub fn point_at(&mut self, position: impl Into<Point>) {
|
||||||
self.cursor = mouse::Cursor::Available(position.into());
|
self.cursor = mouse::Cursor::Available(position.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clicks the [`Target`] found by the given [`Selector`], if any.
|
||||||
|
///
|
||||||
|
/// This consists in:
|
||||||
|
/// - Pointing the mouse cursor at the center of the [`Target`].
|
||||||
|
/// - Simulating a [`click`].
|
||||||
pub fn click(
|
pub fn click(
|
||||||
&mut self,
|
&mut self,
|
||||||
selector: impl Into<Selector>,
|
selector: impl Into<Selector>,
|
||||||
|
|
@ -281,6 +378,7 @@ where
|
||||||
Ok(target)
|
Ok(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simulates a key press, followed by a release, in the [`Simulator`].
|
||||||
pub fn tap_key(&mut self, key: impl Into<keyboard::Key>) -> event::Status {
|
pub fn tap_key(&mut self, key: impl Into<keyboard::Key>) -> event::Status {
|
||||||
self.simulate(tap_key(key, None))
|
self.simulate(tap_key(key, None))
|
||||||
.first()
|
.first()
|
||||||
|
|
@ -288,6 +386,7 @@ where
|
||||||
.unwrap_or(event::Status::Ignored)
|
.unwrap_or(event::Status::Ignored)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simulates a user typing in the keyboard the given text in the [`Simulator`].
|
||||||
pub fn typewrite(&mut self, text: &str) -> event::Status {
|
pub fn typewrite(&mut self, text: &str) -> event::Status {
|
||||||
let statuses = self.simulate(typewrite(text));
|
let statuses = self.simulate(typewrite(text));
|
||||||
|
|
||||||
|
|
@ -296,6 +395,7 @@ where
|
||||||
.fold(event::Status::Ignored, event::Status::merge)
|
.fold(event::Status::Ignored, event::Status::merge)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simulates the given raw sequence of events in the [`Simulator`].
|
||||||
pub fn simulate(
|
pub fn simulate(
|
||||||
&mut self,
|
&mut self,
|
||||||
events: impl IntoIterator<Item = Event>,
|
events: impl IntoIterator<Item = Event>,
|
||||||
|
|
@ -313,6 +413,7 @@ where
|
||||||
statuses
|
statuses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draws and takes a [`Snapshot`] of the interface in the [`Simulator`].
|
||||||
pub fn snapshot(&mut self, theme: &Theme) -> Result<Snapshot, Error> {
|
pub fn snapshot(&mut self, theme: &Theme) -> Result<Snapshot, Error> {
|
||||||
let base = theme.base();
|
let base = theme.base();
|
||||||
|
|
||||||
|
|
@ -357,16 +458,24 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turns the [`Simulator`] into the sequence of messages produced by any interactions.
|
||||||
pub fn into_messages(self) -> impl Iterator<Item = Message> {
|
pub fn into_messages(self) -> impl Iterator<Item = Message> {
|
||||||
self.messages.into_iter()
|
self.messages.into_iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A frame of a user interface rendered by a [`Simulator`].
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Snapshot {
|
pub struct Snapshot {
|
||||||
screenshot: window::Screenshot,
|
screenshot: window::Screenshot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Snapshot {
|
impl Snapshot {
|
||||||
|
/// Compares the [`Snapshot`] with the PNG image found in the given path, returning
|
||||||
|
/// `true` if they are identical.
|
||||||
|
///
|
||||||
|
/// If the PNG image does not exist, it will be created by the [`Snapshot`] for future
|
||||||
|
/// testing and `true` will be returned.
|
||||||
pub fn matches_image(&self, path: impl AsRef<Path>) -> Result<bool, Error> {
|
pub fn matches_image(&self, path: impl AsRef<Path>) -> Result<bool, Error> {
|
||||||
let path = snapshot_path(path, "png");
|
let path = snapshot_path(path, "png");
|
||||||
|
|
||||||
|
|
@ -401,6 +510,11 @@ impl Snapshot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compares the [`Snapshot`] with the SHA-256 hash file found in the given path, returning
|
||||||
|
/// `true` if they are identical.
|
||||||
|
///
|
||||||
|
/// If the hash file does not exist, it will be created by the [`Snapshot`] for future
|
||||||
|
/// testing and `true` will be returned.
|
||||||
pub fn matches_hash(&self, path: impl AsRef<Path>) -> Result<bool, Error> {
|
pub fn matches_hash(&self, path: impl AsRef<Path>) -> Result<bool, Error> {
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
|
@ -427,6 +541,7 @@ impl Snapshot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the sequence of events of a click.
|
||||||
pub fn click() -> impl Iterator<Item = Event> {
|
pub fn click() -> impl Iterator<Item = Event> {
|
||||||
[
|
[
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)),
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)),
|
||||||
|
|
@ -435,6 +550,7 @@ pub fn click() -> impl Iterator<Item = Event> {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the sequence of events of a "key tap" (i.e. pressing and releasing a key).
|
||||||
pub fn tap_key(
|
pub fn tap_key(
|
||||||
key: impl Into<keyboard::Key>,
|
key: impl Into<keyboard::Key>,
|
||||||
text: Option<SmolStr>,
|
text: Option<SmolStr>,
|
||||||
|
|
@ -465,17 +581,27 @@ pub fn tap_key(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the sequence of events of typewriting the given text in a keyboard.
|
||||||
pub fn typewrite(text: &str) -> impl Iterator<Item = Event> + '_ {
|
pub fn typewrite(text: &str) -> impl Iterator<Item = Event> + '_ {
|
||||||
text.chars()
|
text.chars()
|
||||||
.map(|c| SmolStr::new_inline(&c.to_string()))
|
.map(|c| SmolStr::new_inline(&c.to_string()))
|
||||||
.flat_map(|c| tap_key(keyboard::Key::Character(c.clone()), Some(c)))
|
.flat_map(|c| tap_key(keyboard::Key::Character(c.clone()), Some(c)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
/// A test error.
|
||||||
|
#[derive(Debug, Clone, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// No matching widget was found for the [`Selector`].
|
||||||
|
#[error("no matching widget was found for the selector: {0:?}")]
|
||||||
NotFound(Selector),
|
NotFound(Selector),
|
||||||
|
/// An IO operation failed.
|
||||||
|
#[error("an IO operation failed: {0}")]
|
||||||
IOFailed(Arc<io::Error>),
|
IOFailed(Arc<io::Error>),
|
||||||
|
/// The decoding of some PNG image failed.
|
||||||
|
#[error("the decoding of some PNG image failed: {0}")]
|
||||||
PngDecodingFailed(Arc<png::DecodingError>),
|
PngDecodingFailed(Arc<png::DecodingError>),
|
||||||
|
/// The encoding of some PNG image failed.
|
||||||
|
#[error("the encoding of some PNG image failed: {0}")]
|
||||||
PngEncodingFailed(Arc<png::EncodingError>),
|
PngEncodingFailed(Arc<png::EncodingError>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -497,6 +623,15 @@ impl From<png::EncodingError> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_font(font: impl Into<Cow<'static, [u8]>>) -> Result<(), Error> {
|
||||||
|
renderer::graphics::text::font_system()
|
||||||
|
.write()
|
||||||
|
.expect("Write to font system")
|
||||||
|
.load_font(font.into());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn snapshot_path(path: impl AsRef<Path>, extension: &str) -> PathBuf {
|
fn snapshot_path(path: impl AsRef<Path>, extension: &str) -> PathBuf {
|
||||||
path.as_ref().with_extension(extension)
|
path.as_ref().with_extension(extension)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
|
//! Select widgets of a user interface.
|
||||||
use crate::core::text;
|
use crate::core::text;
|
||||||
use crate::core::widget;
|
use crate::core::widget;
|
||||||
|
|
||||||
|
/// A selector describes a strategy to find a certain widget in a user interface.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Selector {
|
pub enum Selector {
|
||||||
|
/// Find the widget with the given [`widget::Id`].
|
||||||
Id(widget::Id),
|
Id(widget::Id),
|
||||||
|
/// Find the widget containing the given [`text::Fragment`].
|
||||||
Text(text::Fragment<'static>),
|
Text(text::Fragment<'static>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,6 +23,7 @@ impl From<&'static str> for Selector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates [`Selector`] that finds the widget containing the given text fragment.
|
||||||
pub fn text(fragment: impl text::IntoFragment<'static>) -> Selector {
|
pub fn text(fragment: impl text::IntoFragment<'static>) -> Selector {
|
||||||
Selector::Text(fragment.into_fragment())
|
Selector::Text(fragment.into_fragment())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue