Skip to main content

app_under_test/
app_under_test.rs

1//! A Bevy app that can be used as an integration test target.
2//! It displays a button that must be clicked. The button is placed at a random position and
3//! moves every 5 seconds.
4//!
5//! Run with the `bevy_remote` and `bevy_feathers` feature enabled:
6//! ```bash
7//! cargo run --example app_under_test --features="bevy_remote bevy_feathers"
8//! ```
9//! This example can be paired with the `integration_test` example, which will run an integration
10//! test on this app.
11
12use bevy::{
13    feathers::{
14        controls::FeathersButton, dark_theme::create_dark_theme, display::caption, theme::UiTheme,
15        FeathersPlugins,
16    },
17    prelude::*,
18    remote::{http::RemoteHttpPlugin, RemotePlugin},
19    time::common_conditions::on_timer,
20    ui::UiGlobalTransform,
21};
22use chacha20::ChaCha8Rng;
23use rand::{RngExt, SeedableRng};
24
25fn main() {
26    App::new()
27        .add_plugins((DefaultPlugins, FeathersPlugins))
28        // To make the app available for integration testing, we add these
29        // remote plugins to expose API’s for a testing framework to call.
30        .add_plugins(RemotePlugin::default())
31        .add_plugins(RemoteHttpPlugin::default())
32        .insert_resource(UiTheme(create_dark_theme()))
33        .insert_resource(SeededRng(ChaCha8Rng::seed_from_u64(19878367467712)))
34        .add_systems(Startup, setup)
35        .add_systems(
36            Update,
37            (
38                move_button.run_if(on_timer(std::time::Duration::from_secs(5))),
39                log_button_position,
40            ),
41        )
42        .run();
43}
44
45#[derive(Resource)]
46struct SeededRng(ChaCha8Rng);
47
48fn on_button_click(_click: On<PointerClick>, mut exit: MessageWriter<AppExit>) {
49    info!("Button pressed!");
50    exit.write(AppExit::Success);
51}
52
53fn log_button_position(
54    transform: Single<&UiGlobalTransform, (With<FeathersButton>, Changed<UiGlobalTransform>)>,
55) {
56    info!(
57        "Button at physical ({}, {})",
58        transform.translation.x, transform.translation.y
59    );
60}
61
62fn random_position(rng: &mut ChaCha8Rng) -> (f32, f32) {
63    let left_pct = rng.random_range(0.0..=60.0);
64    let top_pct = rng.random_range(0.0..=60.0);
65    (left_pct, top_pct)
66}
67
68fn move_button(
69    mut rng: ResMut<SeededRng>,
70    mut button_query: Query<&mut Node, With<FeathersButton>>,
71) {
72    let (left_pct, top_pct) = random_position(&mut rng.0);
73    for mut node in &mut button_query {
74        node.left = percent(left_pct);
75        node.top = percent(top_pct);
76    }
77}
78
79fn setup(mut commands: Commands, mut rng: ResMut<SeededRng>) {
80    let (left_pct, top_pct) = random_position(&mut rng.0);
81    commands.spawn(Camera2d);
82    commands.spawn_scene(bsn! {
83        Node {
84            width: percent(100),
85            height: percent(100),
86        }
87        Children [
88            @FeathersButton {
89                @caption: bsn! { @caption("Button") }
90            }
91            Node {
92                width: px(150),
93                height: px(65),
94                border: UiRect::all(px(5)),
95                left: percent(left_pct),
96                top: percent(top_pct),
97            }
98            BorderColor::all(Color::WHITE)
99            on(on_button_click)
100        ]
101    });
102}