Skip to content

Commit 004e59c

Browse files
tonybiermangenusistimelord
authored andcommitted
Refactored integration test infrastructure to eliminate code duplication and expanded widget test coverage
1 parent fb939cb commit 004e59c

14 files changed

Lines changed: 538 additions & 685 deletions

tests/README.md

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
# Integration Tests Guide
2+
3+
This directory contains integration tests for `iced_aw` widgets using the `iced_test` simulator framework.
4+
5+
## Quick Start
6+
7+
It is suggested that integration test writers try to use the common test helpers to avoid code duplication:
8+
9+
```rust
10+
//! Integration tests for the MyWidget widget
11+
12+
#[macro_use]
13+
mod common;
14+
15+
use iced::Element;
16+
use iced_aw::MyWidget;
17+
18+
#[derive(Clone, Debug)]
19+
enum Message {
20+
MyAction,
21+
}
22+
23+
// Generate test helpers for your Message type
24+
test_helpers!(Message);
25+
26+
#[test]
27+
fn my_widget_renders() {
28+
run_test(|| MyWidget::new().into());
29+
}
30+
31+
#[test]
32+
fn my_widget_shows_text() {
33+
run_test_and_find(|| MyWidget::new().into(), "Expected Text");
34+
}
35+
```
36+
37+
## Available Helpers
38+
39+
The `test_helpers!` macro generates the following for your Message type:
40+
41+
### Types
42+
- **`App`** - Test application wrapper with `new()`, `update()`, and `view()` methods
43+
- **`ViewFn`** - Type alias for view functions: `Box<dyn Fn() -> Element<'static, Message>>`
44+
45+
### Functions
46+
- **`simulator(app: &App) -> Simulator<'_, Message>`**
47+
Creates a simulator with default settings
48+
49+
- **`run_test<F>(view_fn: F)`**
50+
Verifies a widget renders without panicking
51+
```rust
52+
run_test(|| MyWidget::new().into());
53+
```
54+
55+
- **`run_test_and_find<F>(view_fn: F, text: &str)`**
56+
Verifies a widget renders and contains specific text
57+
```rust
58+
run_test_and_find(|| MyWidget::new().into(), "Button Label");
59+
```
60+
61+
## Advanced Usage
62+
63+
### Stateful Tests
64+
65+
For tests requiring state management, it is recommended to use `App` directly:
66+
67+
```rust
68+
#[test]
69+
fn button_click_produces_message() -> Result<(), Error> {
70+
let (mut app, _) = App::new(|| MyWidget::new().into());
71+
let mut ui = simulator(&app);
72+
73+
ui.click("Button")?;
74+
75+
for message in ui.into_messages() {
76+
app.update(message);
77+
}
78+
79+
Ok(())
80+
}
81+
```
82+
83+
### Custom Stateful Apps
84+
85+
For complex state tracking, it is recommended to define a custom app alongside the generated helpers:
86+
87+
```rust
88+
#[derive(Clone)]
89+
struct StatefulTestApp {
90+
counter: std::rc::Rc<std::cell::RefCell<usize>>,
91+
}
92+
93+
impl StatefulTestApp {
94+
fn new() -> (Self, iced::Task<Message>) {
95+
(Self { counter: std::rc::Rc::new(std::cell::RefCell::new(0)) }, iced::Task::none())
96+
}
97+
98+
fn update(&mut self, message: Message) {
99+
match message {
100+
Message::Increment => *self.counter.borrow_mut() += 1,
101+
}
102+
}
103+
104+
fn view(&self) -> Element<'_, Message> {
105+
MyWidget::new(*self.counter.borrow()).into()
106+
}
107+
}
108+
```
109+
110+
## Test Organization
111+
112+
For consistency, it is suggested that you structure your tests with clear section comments:
113+
114+
```rust
115+
// ============================================================================
116+
// Basic Rendering Tests
117+
// ============================================================================
118+
119+
#[test]
120+
fn widget_renders_with_defaults() { /* ... */ }
121+
122+
// ============================================================================
123+
// Interaction Tests
124+
// ============================================================================
125+
126+
#[test]
127+
fn button_click_works() { /* ... */ }
128+
```
129+
130+
## Simulator API
131+
132+
Full API documentation: https://github.com/iced-rs/iced/blob/master/test/src/simulator.rs
133+
134+
Key methods:
135+
- `ui.find(text)` - Locate text in the widget tree
136+
- `ui.click(text)` - Click on element containing text
137+
- `ui.tap_key(key)` - Simulate keyboard input
138+
- `ui.into_messages()` - Consume simulator and get produced messages
139+
140+
## Suggested Best Practices
141+
142+
1. **Use `run_test_and_find` when possible** - Tests both rendering and `operate()` implementation
143+
2. **Keep test names descriptive** - Name should explain what's being tested
144+
3. **Test one thing per test** - Avoid complex multi-step tests
145+
4. **Use `Result<(), Error>` for tests with `?` operator** - Makes error handling cleaner
146+
5. **Keep imports minimal** - Only import what you actually use
147+
148+
## Test Naming Convention
149+
150+
It is suggested that integration test function names begin with the widget name as a prefix.
151+
152+
### Naming Pattern
153+
154+
```rust
155+
fn <widget_name>_<test_description>()
156+
```
157+
158+
### Example test names
159+
160+
**Correct:**
161+
```rust
162+
#[test]
163+
fn badge_renders_with_text() { /* ... */ }
164+
165+
#[test]
166+
fn number_input_increment_button_click_produces_message() { /* ... */ }
167+
168+
#[test]
169+
fn time_picker_displays_12h_format_correctly() { /* ... */ }
170+
```
171+
172+
**Incorrect:**
173+
```rust
174+
#[test]
175+
fn renders_with_text() { /* ... */ } // Missing widget prefix
176+
177+
#[test]
178+
fn can_increment_number() { /* ... */ } // Missing widget prefix
179+
180+
#[test]
181+
fn displays_time() { /* ... */ } // Missing widget prefix
182+
```
183+
184+
This convention helps ensure:
185+
- Tests are clearly associated with their widget
186+
- Test output is easier to read and filter
187+
- grep/search operations are more effective
188+
189+
## Example Test File Structure
190+
191+
```rust
192+
//! Integration tests for the Widget
193+
//!
194+
//! Brief description of what these tests cover.
195+
196+
#[macro_use]
197+
mod common;
198+
199+
use iced::{Element, Length};
200+
use iced_aw::Widget;
201+
use iced_test::{Error, Simulator};
202+
203+
#[derive(Clone, Debug)]
204+
enum Message {
205+
Action,
206+
}
207+
208+
test_helpers!(Message);
209+
210+
// ============================================================================
211+
// Basic Tests
212+
// ============================================================================
213+
214+
#[test]
215+
fn widget_renders() {
216+
run_test(|| Widget::new().into());
217+
}
218+
219+
#[test]
220+
fn widget_with_text() {
221+
run_test_and_find(|| Widget::new().with_label("Test").into(), "Test");
222+
}
223+
224+
// ============================================================================
225+
// Configuration Tests
226+
// ============================================================================
227+
228+
#[test]
229+
fn widget_with_custom_width() {
230+
run_test(|| Widget::new().width(Length::Fill).into());
231+
}
232+
233+
// ============================================================================
234+
// Interaction Tests
235+
// ============================================================================
236+
237+
#[test]
238+
fn widget_click_works() -> Result<(), Error> {
239+
let (mut app, _) = App::new(|| Widget::new().into());
240+
let mut ui = simulator(&app);
241+
242+
ui.click("Target")?;
243+
244+
let mut got_message = false;
245+
for message in ui.into_messages() {
246+
got_message = true;
247+
app.update(message);
248+
}
249+
250+
assert!(got_message);
251+
Ok(())
252+
}
253+
```
254+
## Button Icons Reference
255+
256+
Common icon characters used in widgets (searchable with `ui.find()`):
257+
258+
- `\u{e800}` - cancel (close/cancel buttons)
259+
- `\u{e801}` - down_open (down arrow, dropdowns)
260+
- `\u{e802}` - left_open (left navigation)
261+
- `\u{e803}` - right_open (right navigation)
262+
- `\u{e804}` - up_open (up arrow, increment)
263+
- `\u{e805}` - ok (checkmark/submit)
264+
265+
Or search for their display forms: `" ▲ "`, `" ▼ "`, `" ✓ "`, etc.

tests/badge_integration_tests.rs

Lines changed: 6 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,19 @@
33
//! These tests verify the Badge widget's behavior by actually exercising
44
//! the widget through the iced test framework.
55
6-
use iced::{Alignment, Color, Element, Length, Settings, Theme};
6+
#[macro_use]
7+
mod common;
8+
9+
use iced::{Alignment, Color, Length, Theme};
710
use iced_aw::Badge;
8-
use iced_test::Simulator;
911
use iced_widget::text::Text;
1012

1113
// Message type for the tests (unused but required by iced)
1214
#[derive(Clone, Debug)]
1315
enum Message {}
1416

15-
type ViewFn = Box<dyn Fn() -> Element<'static, Message>>;
16-
17-
#[derive(Clone)]
18-
struct App {
19-
view_fn: std::rc::Rc<ViewFn>,
20-
}
21-
22-
impl App {
23-
fn new<F>(view_fn: F) -> (Self, iced::Task<Message>)
24-
where
25-
F: Fn() -> Element<'static, Message> + 'static,
26-
{
27-
(
28-
App {
29-
view_fn: std::rc::Rc::new(Box::new(view_fn)),
30-
},
31-
iced::Task::none(),
32-
)
33-
}
34-
35-
fn view(&self) -> Element<'_, Message> {
36-
(self.view_fn)()
37-
}
38-
}
39-
40-
/// Helper to run a test with a given view
41-
fn run_test<F>(view_fn: F)
42-
where
43-
F: Fn() -> Element<'static, Message> + 'static,
44-
{
45-
let (app, _) = App::new(view_fn);
46-
let _ui = Simulator::with_settings(Settings::default(), app.view());
47-
// The widget is successfully rendered if we get here without panicking
48-
}
49-
50-
/// Helper to verify text can be found (tests operate() implementation)
51-
fn run_test_and_find<F>(view_fn: F, text: &str)
52-
where
53-
F: Fn() -> Element<'static, Message> + 'static,
54-
{
55-
let (app, _) = App::new(view_fn);
56-
let mut ui = Simulator::with_settings(Settings::default(), app.view());
57-
assert!(
58-
ui.find(text).is_ok(),
59-
"Failed to find text '{}' in badge",
60-
text
61-
);
62-
}
17+
// Generate test helpers for this Message type
18+
test_helpers!(Message);
6319

6420
#[test]
6521
fn badge_renders_with_text() {

0 commit comments

Comments
 (0)