Files
effectcraft/crates/ui-egui/tests/it/ui_effect_names.rs
T
aiongg 0173abde8c CI: dev-profile gate, one test binary per crate, nextest, line tables only (#2235)
* Dev profile: line tables only for workspace crates, no debug info for dependencies

* xtask ci: dev-profile gate, nextest, tests without examples, wasm --keep-going

* One integration-test binary per crate

* xtask ci: run effectcraft-gpu's shared-device tests in one cargo test process

* xtask ci: run every stage and report all failures

* xtask ci: name the stage type (clippy type_complexity)

* xtask ci: run every test pass and report all failures

* cargo fmt
2026-10-23 12:59:51 -04:00

138 lines
6.6 KiB
Rust

//! Effect names in a Simplified Chinese interface (#821): the generated Effect menu and Effect
//! Controls draw the catalog's name, while the effect ids, the property tree's group name, the
//! automation labels and `effect.list` stay English.
use effectcraft_engine::Session;
use effectcraft_engine::project::LayerId;
use effectcraft_ui_egui::EffectcraftApp;
use effectcraft_ui_egui::dock::PanelKind;
use effectcraft_ui_egui::native_menu::{NativeNode, build as native_menu};
use egui::vec2;
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use serde_json::json;
/// The effect under test, and the names the registry and the catalog give it.
const BLUR: &str = "ec.blur.gaussian";
const BLUR_EN: &str = "Gaussian Blur";
const BLUR_ZH: &str = "高斯模糊";
fn harness(language: &str) -> (Harness<'static, EffectcraftApp>, u64) {
let mut s = Session::default();
s.execute("comp.new", json!({"name": "Main", "width": 640, "height": 360, "frameRate": 30, "duration": 10})).unwrap();
s.execute("layer.newSolid", json!({"name": "Back", "color": "#20c040"})).unwrap();
let layer = s.execute("layer.newSolid", json!({"name": "Blurred", "color": "#ff0000", "width": 200, "height": 100})).unwrap()["layer"].as_u64().unwrap();
let fx = s.execute("effect.apply", json!({"layers": [layer], "effect": BLUR})).unwrap()["effects"][0].as_u64().unwrap();
// A second instance: the engine stores it as "Gaussian Blur 2", which the catalog must keep.
s.execute("effect.apply", json!({"layers": [layer], "effect": BLUR})).unwrap();
s.state.selected_layers = vec![LayerId(layer)];
s.state.selected_props.clear();
s.prefs.general.language = language.to_string();
let mut app = EffectcraftApp::new(s);
app.show_panel(PanelKind::EffectControls);
app.show_panel(PanelKind::EffectsPresets);
app.show_panel(PanelKind::Timeline);
let mut h = Harness::builder().with_size(vec2(1400.0, 900.0)).build_eframe(|_| app);
h.run_steps(4);
(h, fx)
}
/// How many widgets draw a label containing `needle` (the Effect Controls header registers its
/// drawn name with the accessibility tree, so a translated name shows up here).
fn drawn(h: &Harness<'_, EffectcraftApp>, needle: &str) -> usize {
h.query_all_by_label_contains(needle).count()
}
/// The label the native menu gives the `effect.apply` entry for effect `id`, under top-level menu
/// `top` and category `category`. The in-window menu bar builds its labels through the same
/// `entry_label`, so this covers both menu bars.
fn menu_label(h: &Harness<'_, EffectcraftApp>, top: &str, category: &str, id: &str) -> Option<String> {
let menu = native_menu(h.state());
let effect = menu.menus.iter().find_map(|n| match n {
NativeNode::Submenu { label, children } if label == top => Some(children),
_ => None,
})?;
effect.iter().find_map(|cat| match cat {
NativeNode::Submenu { label, children } if label == category => children.iter().find_map(|item| match item {
NativeNode::Item(i) if i.command == "effect.apply" && i.params.get("effect").and_then(|v| v.as_str()) == Some(id) => Some(i.label.clone()),
_ => None,
}),
_ => None,
})
}
/// Effect Controls draws the catalog's name, including the instance index the engine appends to
/// a repeat; the English interface draws the registry's name.
#[test]
fn the_effect_controls_header_follows_the_interface_language() {
let (h, _fx) = harness("zh-hans");
assert!(drawn(&h, BLUR_ZH) > 0, "the header draws the Chinese name");
assert_eq!(drawn(&h, "高斯模糊 2"), 1, "the second instance keeps its index");
assert_eq!(drawn(&h, BLUR_EN), 0, "nothing draws the English name");
let (en, _fx) = harness("en");
assert!(drawn(&en, BLUR_EN) > 0);
assert_eq!(drawn(&en, BLUR_ZH), 0);
}
/// The generated Effect menu — its category submenus and its entries — follows the catalog.
#[test]
fn the_generated_effect_menu_is_translated() {
let (h, _fx) = harness("zh-hans");
assert_eq!(menu_label(&h, "效果", "模糊和锐化", BLUR).as_deref(), Some(BLUR_ZH), "the generated entry is translated");
let (en, _fx) = harness("en");
assert_eq!(menu_label(&en, "Effect", "Blur & Sharpen", BLUR).as_deref(), Some(BLUR_EN), "the English menu is unchanged");
}
/// Right-click the Effect Controls header of effect `fx`.
fn open_effect_menu(h: &mut Harness<'_, EffectcraftApp>, fx: u64) {
let e = h.state().auto.find(&format!("effectControls.effect.{fx}")).expect("the effect's header").rect;
let pos = egui::pos2(e[0] + e[2] / 2.0, e[1] + e[3] / 2.0);
h.event(egui::Event::PointerMoved(pos));
h.step();
for pressed in [true, false] {
h.event(egui::Event::PointerButton { pos, button: egui::PointerButton::Secondary, pressed, modifiers: Default::default() });
h.step();
}
h.run_steps(2);
}
/// The effect header's context menu draws its items in the interface language, like the layer
/// menu (#1308): it used to draw Copy, Paste, Duplicate, Reset, Remove, Move Up and Move Down in
/// English whatever the language.
#[test]
fn the_effect_context_menu_follows_the_interface_language() {
const ENGLISH: [&str; 7] = ["Copy", "Paste", "Duplicate", "Reset", "Remove", "Move Up", "Move Down"];
let (mut h, fx) = harness("zh-hans");
open_effect_menu(&mut h, fx);
for label in ["复制", "粘贴", "重复", "重命名", "重置", "移除", "上移", "下移"] {
assert!(h.query_by_label(label).is_some(), "{label} missing from the effect menu");
}
for label in ENGLISH {
assert!(h.query_by_label(label).is_none(), "{label} drawn in English");
}
let (mut en, fx) = harness("en");
open_effect_menu(&mut en, fx);
for label in ENGLISH {
assert!(en.query_by_label(label).is_some(), "{label} missing from the English menu");
}
}
/// Everything an agent or a script addresses stays English: the automation label, the property
/// tree's group name and `effect.list`.
#[test]
fn the_ids_automation_labels_and_effect_list_stay_english() {
let (mut h, fx) = harness("zh-hans");
assert_eq!(
h.state().auto.find(&format!("effectControls.effect.{fx}")).map(|e| e.label.clone()),
Some(BLUR_EN.to_string()),
"the automation label is the property tree's name"
);
let listed = h.state_mut().session.execute("effect.list", json!({})).unwrap();
let entry = listed.as_array().and_then(|a| a.iter().find(|e| e["id"] == BLUR)).cloned().unwrap_or_default();
assert_eq!(entry["name"], BLUR_EN, "effect.list keeps the registry's name");
assert_eq!(entry["category"], "Blur & Sharpen", "effect.list keeps the registry's category");
}