Files
effectcraft/crates/ui-egui/tests/it/ui_script_console_keyboard.rs
48c8137835 Accessibility (gap 25), slice 2: Info, Flowchart, Script Console and the 3D Camera Tracker block named, described and keyboard-operable (builds on #1339) (#1341)
* #1335: accessibility slice 2: Info, Flowchart, Script Console and the 3D Camera Tracker block named, described and keyboard-operable

The audit gets read-outs, graphics and logs as described items (a name is required in a fixed panel), dedicated maximized scenarios for the panels the shared loop squeezes, and minimum counts for the new zero panels.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Review: Enter/Space on a favourite star only toggles it; keyboard activation of a property label; settings page scrolls to focus; sharper UI test assertions

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Refresh the accessibility audit baseline after merging upstream/main

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* a11y audit: hold the machine-dependent Media Browser scenario to its upper bound

The Media Browser opens on the home folder, so a runner with fewer files finds fewer controls and
the 'improved, lower the entry' check failed in CI. The entry stays an upper bound.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Merge upstream/main and updated feat-gap25-a11y-audit into feat-gap25-a11y-panels-2

Brought in the updated feat-gap25-a11y-audit (which already contains upstream/main). Conflict: crates/ui-egui/src/menus.rs, which upstream split into menus/*.rs. Took the slim module and re-applied this branch's Flowchart hunks where the code now lives: nudge() skips the Flowchart (menus/invoke.rs) and Enter is left to the Flowchart in handle_shortcuts (menus/shortcuts.rs).

Also removed a duplicate MACHINE_DEPENDENT const in tests/ui_accessibility_audit.rs that the two branches both added. The audit BASELINE still matches the measured findings.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Flowchart: split show() to pass the stricter clippy limits from main

Main now denies cognitive_complexity (30) and too_many_arguments; the
merged flowchart panel exceeded it. The keyboard navigation, the
key-to-node mapping, pan-into-view, node commands, the accessible node
name and the option params move into small helpers with unchanged
behaviour.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Camera tracker a11y test: wait for the analysis result instead of one frame

The test pressed Enter, ran one frame and expected to see the background job.
On a fast runner the job starts, ends and is written back inside that frame,
so the state was never seen. Undo the first analysis, press Enter, and wait
(bounded) for the new analysis step in the undo history.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Accessibility: one hours range for the accessibility pass in ui-parity.md; test that Flowchart automation labels stay English

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Flowchart toolbar: keep the English label for the automation registration

The translated text is shown and spoken; the automation label stays English.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-06 19:12:07 +01:00

108 lines
4.9 KiB
Rust

//! Script Console accessible names and keyboard operation (docs/gaps.md gap 25): the input, the
//! output (a log), Run and Clear are named and reachable with Tab, Tab leaves the input instead of
//! typing a tab character, and Enter / Space work the buttons.
use effectcraft_engine::Session;
use effectcraft_ui_egui::EffectcraftApp;
use effectcraft_ui_egui::dock::PanelKind;
use egui::accesskit::Role;
use egui::{Event, Key, Modifiers};
use egui_kittest::Harness;
use egui_kittest::kittest::{NodeT, Queryable};
use serde_json::json;
const INPUT: &str = "Script… (⌘/Ctrl+Enter runs)";
fn harness() -> Harness<'static, EffectcraftApp> {
let mut s = Session::default();
s.execute("comp.new", json!({"name": "Main", "width": 320, "height": 180, "duration": 4})).unwrap();
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| EffectcraftApp::new(s));
h.state_mut().show_panel(PanelKind::ScriptConsole);
h.state_mut().ui.maximized = Some(PanelKind::ScriptConsole);
h.run_steps(4);
h
}
fn key(h: &mut Harness<'_, EffectcraftApp>, key: Key, modifiers: Modifiers) {
for pressed in [true, false] {
h.input_mut().events.push(Event::Key { key, physical_key: None, pressed, repeat: false, modifiers });
h.step();
}
h.run_steps(3);
}
fn focused(h: &Harness<'_, EffectcraftApp>) -> Option<(Role, String)> {
h.root().children_recursive().find(|n| n.is_focused()).map(|n| (n.accesskit_node().role(), n.accesskit_node().label().unwrap_or_default()))
}
#[test]
fn input_output_and_buttons_are_named() {
let mut h = harness();
assert_eq!(h.get_by_role_and_label(Role::MultilineTextInput, INPUT).accesskit_node().role(), Role::MultilineTextInput);
let log = h.get_by_role_and_label(Role::Log, "Script Console output");
assert!(log.accesskit_node().value().is_none_or(|v| v.is_empty()), "an empty console has an empty log");
// Run is unavailable with nothing typed; Clear with nothing to clear.
assert!(h.get_by_role_and_label(Role::Button, "Run").accesskit_node().is_disabled());
assert!(h.get_by_role_and_label(Role::Button, "Clear").accesskit_node().is_disabled());
h.state_mut().ui.script_console.input = "1 + 1".into();
h.state_mut().ui.script_console.record("1 + 1", &Ok(json!({"result": 2})));
h.run_steps(4);
assert!(!h.get_by_role_and_label(Role::Button, "Run").accesskit_node().is_disabled());
assert!(!h.get_by_role_and_label(Role::Button, "Clear").accesskit_node().is_disabled());
let text = h.get_by_role_and_label(Role::Log, "Script Console output").accesskit_node().value().unwrap_or_default();
assert!(text.contains("> 1 + 1") && text.contains('2'), "the log carries the output lines: {text:?}");
}
#[test]
fn tab_leaves_the_input_and_reaches_run_and_clear() {
let mut h = harness();
h.state_mut().ui.script_console.input = "x".into();
h.state_mut().ui.script_console.record("x", &Ok(json!({"result": 1})));
h.run_steps(3);
h.get_by_role_and_label(Role::MultilineTextInput, INPUT).focus();
h.run_steps(3);
assert_eq!(focused(&h).map(|f| f.0), Some(Role::MultilineTextInput));
let mut seen = vec![];
for _ in 0..6 {
key(&mut h, Key::Tab, Modifiers::NONE);
if let Some(f) = focused(&h) {
seen.push(f);
}
}
assert_eq!(h.state().ui.script_console.input, "x", "Tab does not type a tab character into the script");
for want in ["Run", "Clear"] {
assert!(seen.iter().any(|(r, n)| *r == Role::Button && n == want), "Tab reaches {want}; reached {seen:?}");
}
}
#[test]
fn enter_runs_the_script_and_space_clears_the_log_from_the_buttons() {
let mut h = harness();
h.state_mut().ui.script_console.input = "app.project.numItems".into();
h.run_steps(3);
h.get_by_role_and_label(Role::Button, "Run").focus();
h.run_steps(3);
assert_eq!(focused(&h), Some((Role::Button, "Run".to_string())));
key(&mut h, Key::Enter, Modifiers::NONE);
assert!(h.state().ui.script_console.input.is_empty(), "Run ran the script and cleared the input");
assert!(h.state().ui.script_console.log.len() >= 2, "its input and result are in the log: {:?}", h.state().ui.script_console.log);
h.get_by_role_and_label(Role::Button, "Clear").focus();
h.run_steps(3);
key(&mut h, Key::Space, Modifiers::NONE);
assert!(h.state().ui.script_console.log.is_empty(), "Space on Clear empties the log");
}
#[test]
fn ctrl_enter_runs_from_the_input() {
let mut h = harness();
h.get_by_role_and_label(Role::MultilineTextInput, INPUT).focus();
h.run_steps(3);
h.state_mut().ui.script_console.input = "1 + 2".into();
h.run_steps(2);
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::COMMAND));
key(&mut h, Key::Enter, Modifiers::COMMAND);
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::NONE));
h.run_steps(2);
assert!(h.state().ui.script_console.log.iter().any(|l| l.text.contains("> 1 + 2")), "Ctrl+Enter ran it");
}