Files
effectcraft/crates/ui-egui/tests/ui_effect_names.rs
Marco Arnulfo f8d82b7265 i18n: the effect and property context menus draw their items in the interface language too (#1308)
The Effect Controls header menu and the Timeline property menu build their
items with the same local closure the layer menu used, so they still drew
English. Both now go through i18n::context_item; Copy, Paste, Edit Value…
and Remove Expression get panel catalog rows.
2026-10-15 21:23:14 +02: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");
}