1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Accessibility-first test utilities for `WaterUI`, on Hydrolysis's two
//! headless pipelines.
//!
//! `waterui-testing` runs inside ordinary `cargo test` targets. [`ui`] builds
//! a test session on the *semantic* runtime — a GPU-free pipeline whose
//! accessibility tree is a product of the view tree and the widgets'
//! semantics, so it carries no style package and answers no geometry:
//! [`UiBuilder::mount`] is all a `UiBuilder<NoStyle>` offers. Tests that need
//! pixels, bounds, pointer gestures or frame timing carry a
//! [`hydrolysis::Style`] through [`UiBuilder::theme`] and mount the *rendered*
//! runtime through `mount_offscreen` — the distinction is in the type, not a
//! runtime check, so a semantic query has no `bounds()` method.
//!
//! # `cargo test` Integration
//!
//! ```ignore
//! fn login_view() -> impl waterui::View {
//! waterui::text("Login").body()
//! }
//!
//! #[waterui::test(login_view)]
//! fn login_smoke(app: &mut waterui_testing::SemanticApp) {
//! app.query()
//! .role(waterui_testing::Role::LABEL)
//! .label("Login")
//! .assert_exists();
//! }
//! ```
//!
//! For tests that own `Binding`s the view closes over, omit the view path and
//! take the configured [`UiBuilder`] by value (the manual-mount form):
//!
//! ```ignore
//! #[waterui::test]
//! fn stepper_updates(ui: waterui_testing::UiBuilder) {
//! let value = waterui::Binding::i32(2);
//! let value_for_view = value.clone();
//! let mut app = ui.mount(move || stepper("Limited", &value_for_view));
//! app.query().label("Limited").increment();
//! assert_eq!(value.get(), 3);
//! }
//! ```
//!
//! A rendered test names its style with `theme =`, and a whole [`App`]
//! mounts through [`mount_app`] — or `ui().theme(style).mount_app(app)` when
//! the session needs its own viewport, runtime flavor or scale factor:
//!
//! ```ignore
//! #[waterui::test(login_view, theme = hydrolysis_m3::Material3::defaults(), offscreen)]
//! fn login_rendered(app: &mut waterui_testing::OffscreenApp) {
//! let snapshot = app.snapshot();
//! assert_eq!(snapshot.width, 390);
//! }
//! ```
//!
//! The `#[waterui::test(...)]` macro expands to a regular `#[test]`, so these
//! tests run under the normal Rust test harness and on GitHub Actions without a
//! custom runner.
//!
//! # Interactions, waits, and time
//!
//! Interactions (`tap`, `set_text`, `increment`, ...) return `()` and panic
//! when the runtime reports the accessibility action unhandled — a plain call
//! is the assertion. After every interaction the session settles to real
//! quiescence (no queued input, no spawned work, no scheduled animations or
//! patches) instead of sleeping. The animation clock is virtual: each pump
//! advances it exactly one frame, so transition sampling is deterministic;
//! [`OffscreenApp::pump_for`] lands on an exact phase of a transition, and
//! waits (`wait_for_existence`, `SemanticApp::wait_for`) pump hot while work
//! is scheduled and only touch wall-clock time for work outside the runtime.
//!
//! Use semantic queries to resolve an [`ElementRef`], then drive interactions
//! through that handle or use it to scope later queries with [`Query::within`].
//! Views tagged with `.a11y_id("login.submit")` resolve via
//! `Selector::identifier` / `Query::identifier`.
//!
//! # Snapshot Artifacts
//!
//! ```ignore
//! use waterui::Environment;
//! use waterui::ViewExt as _;
//! use waterui::graphics::color::Srgb;
//! use waterui_testing::TestHost;
//!
//! let host = TestHost::new(
//! Environment::new(),
//! 320,
//! 180,
//! hydrolysis_m3::Material3::defaults(),
//! );
//! let captured = host.capture_snapshot(
//! waterui::text("Preview")
//! .body()
//! .foreground(Srgb::WHITE)
//! .background(Srgb::BLACK),
//! "docs/visual",
//! "text-preview",
//! "00_initial",
//! );
//! assert!(captured.path().is_file());
//! ```
//!
//! When `WATERUI_TEST_ARTIFACTS_DIR` is set, snapshots are written beneath that
//! directory using `WaterUI`'s canonical `<suite>/<case>/<stage>.png` layout. The
//! repository's GitHub workflows already upload and summarize those snapshot images.
pub
pub
pub use Role as AccessKitRole;
pub use ;
pub use ;
pub use ;
pub use drain_parked_local_work;
pub use ;
pub use ;
pub use ;
pub use Query;
pub use ;
pub use ;
pub use ;
pub use ;
/// Internal async bridge used by `#[waterui::test(...)]` expansion.