diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs index 9b691cf7..a208511f 100644 --- a/crates/ui-egui/src/panels.rs +++ b/crates/ui-egui/src/panels.rs @@ -66,6 +66,7 @@ pub fn tracks_and_groups(app: &mut SoundApp, ui: &mut Ui) { let out = lists.show(ui, |ui| { for (id, name, shown, color, sel) in tracks { let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 17.0), Sense::click_and_drag()); + resp.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::SelectableLabel, true, sel, &name)); if sel { ui.painter().rect_filled(r, 0.0, t.row_selected); } @@ -94,6 +95,7 @@ pub fn tracks_and_groups(app: &mut SoundApp, ui: &mut Ui) { let (r, _) = ui.allocate_exact_size(vec2(ui.available_width(), 17.0), Sense::hover()); ui.painter().text(pos2(r.min.x + 30.0, r.center().y), Align2::LEFT_CENTER, "", regular(11.5).clone(), t.text); let all = ui.interact(r, ui.id().with("group_all"), Sense::click()); + all.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, "")); if all.double_clicked() { let _ = app.run("edit.select_all", json!({})); } @@ -105,6 +107,7 @@ pub fn tracks_and_groups(app: &mut SoundApp, ui: &mut Ui) { }); for (id, letter, name, active, color) in groups { let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 17.0), Sense::click()); + resp.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, true, active, &name)); if active { ui.painter().rect_filled(Rect::from_min_size(pos2(r.min.x + 4.0, r.min.y + 2.0), vec2(14.0, 13.0)), 1.0, rgb(color)); } diff --git a/crates/ui-egui/tests/mix_instrument_contrast.rs b/crates/ui-egui/tests/mix_instrument_contrast.rs index 663cab3e..82ad099a 100644 --- a/crates/ui-egui/tests/mix_instrument_contrast.rs +++ b/crates/ui-egui/tests/mix_instrument_contrast.rs @@ -177,7 +177,7 @@ fn populated_mix_instrument_uses_readable_palette_in_real_frames() { harness.step(); } harness.step(); // Let the popup finish its initial sizing pass. - harness.get_by_label_contains("Built-in").hover(); + harness.get_by_label("Built-in ⏵").hover(); harness.step(); harness.step(); harness.get_by_label("Drum Synth").click(); diff --git a/crates/ui-egui/tests/side_lists_access.rs b/crates/ui-egui/tests/side_lists_access.rs new file mode 100644 index 00000000..c44bc4ec --- /dev/null +++ b/crates/ui-egui/tests/side_lists_access.rs @@ -0,0 +1,54 @@ +//! #651: the Tracks and Groups lists publish their rows to AccessKit, by name, so screen readers +//! can read them and tests can reach them by label instead of by geometry. + +use egui::accesskit::{Role, Toggled}; +use egui::vec2; +use egui_kittest::{ + Harness, + kittest::{NodeT, Queryable}, +}; +use serde_json::json; +use soundcraft_ui_egui::{Services, SoundApp}; + +fn harness() -> Harness<'static, SoundApp> { + let mut app = SoundApp::new(soundcraft_engine::demo::demo_engine(), None, Services::default()); + let first = app.engine.session().tracks.first().map(|t| t.id.0).unwrap_or_default(); + app.run("track.group", json!({"name": "Rhythm", "tracks": [first]})).unwrap(); + app.engine.session_mut().edit.selected_tracks.clear(); + let mut h = Harness::builder().with_size(vec2(1600.0, 1000.0)).build_ui_state( + |ui, app: &mut SoundApp| { + let ctx = ui.ctx().clone(); + app.logic(&ctx); + app.ui(ui); + }, + app, + ); + h.run_steps(3); + h +} + +#[test] +fn tracks_list_rows_are_named_and_report_their_selection() { + let mut h = harness(); + let tracks: Vec<(u64, String)> = h.state().engine.session().tracks.iter().map(|t| (t.id.0, t.name.clone())).collect(); + assert!(tracks.len() >= 2); + for (_, name) in &tracks { + assert_eq!(h.get_by_role_and_label(Role::Button, name).accesskit_node().toggled(), Some(Toggled::False), "{name}"); + } + let (id, name) = &tracks[1]; + h.get_by_role_and_label(Role::Button, name).click(); + h.run_steps(2); + assert_eq!(h.state().engine.session().edit.selected_tracks.iter().map(|t| t.0).collect::>(), vec![*id]); + assert_eq!(h.get_by_role_and_label(Role::Button, name).accesskit_node().toggled(), Some(Toggled::True)); +} + +#[test] +fn groups_list_rows_are_named_and_report_whether_the_group_is_active() { + let mut h = harness(); + h.get_by_label(""); + assert_eq!(h.get_by_role_and_label(Role::CheckBox, "Rhythm").accesskit_node().toggled(), Some(Toggled::True)); + h.get_by_role_and_label(Role::CheckBox, "Rhythm").click(); + h.run_steps(2); + assert!(h.state().engine.session().groups.iter().all(|g| !g.active)); + assert_eq!(h.get_by_role_and_label(Role::CheckBox, "Rhythm").accesskit_node().toggled(), Some(Toggled::False)); +}