From a0e078c65d21a4ae1493d72d72aad2e43f4c3629 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 13:35:12 +0000 Subject: [PATCH] test: Add tests for sequence control button - Wrote tests for `sequence_control_button_system`. - Fixed panic-inducing `unwrap` by using `let-else` to ensure code safety. - Restored missing background reset logic in `Interaction::None` state. - Applied `GHOST_BORDER` style to hover states in compliance with design system guidelines. Co-authored-by: dynamikdev <717692+dynamikdev@users.noreply.github.com> --- src/ui/mod.rs | 2 + src/ui/systems.rs | 12 +++- src/ui/systems_test.rs | 142 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 src/ui/systems_test.rs diff --git a/src/ui/mod.rs b/src/ui/mod.rs index f77092d..ba51e35 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -12,6 +12,8 @@ mod setup_test; #[cfg(test)] mod curriculum_test; pub mod systems; +#[cfg(test)] +mod systems_test; pub mod target; use bevy::prelude::*; diff --git a/src/ui/systems.rs b/src/ui/systems.rs index 88e2ffb..84bddc6 100644 --- a/src/ui/systems.rs +++ b/src/ui/systems.rs @@ -9,7 +9,7 @@ use bevy_ui_widgets::{Slider, SliderRange, SliderThumb, SliderValue}; use std::f32::consts::PI; use crate::components::*; -use crate::constants::{PRIMARY_EMISSIVE, NEUTRAL_TEXT, PANEL_WIDTH}; +use crate::constants::{PRIMARY_EMISSIVE, NEUTRAL_TEXT, PANEL_WIDTH, GHOST_BORDER}; use crate::resources::*; /// Handles interactions with the sequence control button (Play/Pause). @@ -28,7 +28,8 @@ pub fn sequence_control_button_system( mut rhythm_state: ResMut, ) { for (interaction, mut background_color, children) in &mut interaction_query { - let mut text = text_query.get_mut(children[0]).unwrap(); + let Some(&child) = children.first() else { continue; }; + let Ok(mut text) = text_query.get_mut(child) else { continue; }; match *interaction { Interaction::Pressed => { sequence_state.running = !sequence_state.running; @@ -44,9 +45,14 @@ pub fn sequence_control_button_system( } } Interaction::Hovered => { - background_color.0 = Color::srgba(0.886, 0.886, 0.886, 0.1); + background_color.0 = GHOST_BORDER; } Interaction::None => { + if sequence_state.running { + background_color.0 = PRIMARY_EMISSIVE; + } else { + background_color.0 = Color::NONE; + } } } } diff --git a/src/ui/systems_test.rs b/src/ui/systems_test.rs new file mode 100644 index 0000000..3a7c678 --- /dev/null +++ b/src/ui/systems_test.rs @@ -0,0 +1,142 @@ +#[cfg(test)] +mod tests { + use bevy::prelude::*; + use crate::components::SequenceControlButton; + use crate::constants::{PRIMARY_EMISSIVE, GHOST_BORDER}; + use crate::resources::{RhythmMode, RhythmState, SequenceMode, SequenceState}; + use crate::ui::systems::sequence_control_button_system; + + /// Helper function to create a minimal Bevy app for testing the sequence_control_button_system + fn setup_test_app() -> App { + let mut app = App::new(); + // Add minimal plugins required to avoid 'TaskPool has not been initialized yet' panics + app.add_plugins(TaskPoolPlugin::default()); + + app.insert_resource(SequenceState { + running: false, + mode: SequenceMode::Random, + current_ordered_value: 0, + }); + + app.insert_resource(RhythmState { + duration: 1.0, + mode: RhythmMode::Constant, + accelerate_counter: 0, + }); + + app.add_systems(Update, sequence_control_button_system); + + app + } + + #[test] + fn test_sequence_control_button_pressed_starts_sequence() { + let mut app = setup_test_app(); + + // Ensure sequence state starts as not running + assert!(!app.world().resource::().running); + + let text_entity = app.world_mut().spawn(Text::new("Launch Sequence")).id(); + let button_entity = app.world_mut().spawn(( + SequenceControlButton, + Interaction::Pressed, + BackgroundColor(Color::NONE), + )).add_child(text_entity).id(); + + app.update(); + + // Sequence should now be running + let sequence_state = app.world().resource::(); + assert!(sequence_state.running); + + // Text and color should be updated + let text = app.world().get::(text_entity).unwrap(); + assert_eq!(text.0, "Stop Sequence"); + + let background_color = app.world().get::(button_entity).unwrap(); + assert_eq!(background_color.0, PRIMARY_EMISSIVE); + } + + #[test] + fn test_sequence_control_button_pressed_stops_sequence() { + let mut app = setup_test_app(); + + // Set state as if it was already running + app.world_mut().resource_mut::().running = true; + app.world_mut().resource_mut::().accelerate_counter = 5; + + let text_entity = app.world_mut().spawn(Text::new("Stop Sequence")).id(); + let button_entity = app.world_mut().spawn(( + SequenceControlButton, + Interaction::Pressed, + BackgroundColor(PRIMARY_EMISSIVE), + )).add_child(text_entity).id(); + + app.update(); + + // Sequence should now be stopped + let sequence_state = app.world().resource::(); + assert!(!sequence_state.running); + + // Rhythm state should have its counter reset + let rhythm_state = app.world().resource::(); + assert_eq!(rhythm_state.accelerate_counter, 0); + + // Text and color should be updated + let text = app.world().get::(text_entity).unwrap(); + assert_eq!(text.0, "Launch Sequence"); + + let background_color = app.world().get::(button_entity).unwrap(); + assert_eq!(background_color.0, Color::NONE); + } + + #[test] + fn test_sequence_control_button_hovered() { + let mut app = setup_test_app(); + + let text_entity = app.world_mut().spawn(Text::new("Launch Sequence")).id(); + let button_entity = app.world_mut().spawn(( + SequenceControlButton, + Interaction::Hovered, + BackgroundColor(Color::NONE), + )).add_child(text_entity).id(); + + app.update(); + + let background_color = app.world().get::(button_entity).unwrap(); + assert_eq!(background_color.0, GHOST_BORDER); + } + + #[test] + fn test_sequence_control_button_none_restores_color() { + let mut app = setup_test_app(); + + let text_entity = app.world_mut().spawn(Text::new("Launch Sequence")).id(); + let button_entity = app.world_mut().spawn(( + SequenceControlButton, + Interaction::None, // Emulate changed to None + BackgroundColor(GHOST_BORDER), // It was hovered before + )).add_child(text_entity).id(); + + // Ensure button component is marked as changed by artificially sending an update + app.update(); + app.world_mut().get_mut::(button_entity).unwrap().set_changed(); + app.update(); + + // Since running is false, it should restore to Color::NONE + let background_color = app.world().get::(button_entity).unwrap(); + assert_eq!(background_color.0, Color::NONE); + + // Now test when running is true + app.world_mut().resource_mut::().running = true; + + // Emulate hovering -> None + app.world_mut().get_mut::(button_entity).unwrap().0 = GHOST_BORDER; + app.world_mut().get_mut::(button_entity).unwrap().set_changed(); + + app.update(); + + let background_color = app.world().get::(button_entity).unwrap(); + assert_eq!(background_color.0, PRIMARY_EMISSIVE); + } +}