Files
vectorcraft/crates/ui-egui/src/tests_labels.rs
T
bflatasticandClaude Opus 5.5 f12428264c Fix #36: the font menus list the installed fonts, with a search field
Type > Font, the Character, Properties and Glyphs panels and Find Font list
every installed font by default, as the reference app does, instead of the
four bundled families. The family dropdowns get a search field that filters
the list as you type (Enter picks the first match) and open on the current
font. Show System Fonts, which no longer means anything, becomes Refresh Font
List (text.rescanFonts): it picks up fonts installed since the app started and
redraws type set in them without editing the document. text.fontList lets MCP
agents list the families and a family's styles.

Root cause: the font lists came from FontDb::families(), which only held the
bundled fonts until something scanned the system fonts (fixed with #130). The
Type > Font menu, rebuilt every frame, now builds its items once per font
change, and renderers lay type out again when the fonts change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:49:22 +07:00

121 lines
4.9 KiB
Rust

//! Neutral wording in menus and panels: what users read names VectorCraft's own features.
use egui::epaint::Shape;
use serde_json::json;
use vectorcraft_engine::Session;
use crate::VectorcraftApp;
use crate::menus::{self, Item};
fn app() -> VectorcraftApp {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 100, "height": 100})).unwrap();
app
}
/// Every string painted by `f` in a headless frame (app fonts installed), one per line. The second
/// of two frames: windows size themselves invisibly in their first.
pub(crate) fn painted_text(app: &mut VectorcraftApp, mut f: impl FnMut(&mut VectorcraftApp, &mut egui::Ui)) -> String {
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
ctx.run_ui(egui::RawInput::default(), |ui| f(app, ui)).textures_delta.clear();
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| f(app, ui));
out.textures_delta.clear();
shapes_text(&out)
}
/// Every string a frame painted, one per line.
pub(crate) fn shapes_text(out: &egui::FullOutput) -> String {
fn collect(s: &Shape, out: &mut String) {
match s {
Shape::Text(t) => {
out.push_str(t.galley.text());
out.push('\n');
}
Shape::Vec(v) => v.iter().for_each(|s| collect(s, out)),
_ => {}
}
}
let mut text = String::new();
for c in &out.shapes {
collect(&c.shape, &mut text);
}
text
}
#[test]
fn effect_menu_has_a_vector_effects_section() {
let tree = menus::menu_tree();
let (_, effect) = tree.iter().find(|(t, _)| *t == "Effect").expect("Effect menu");
assert!(effect.iter().any(|i| matches!(i, Item::Header("Vector Effects"))));
}
#[test]
fn library_submenus_are_disabled_placeholders() {
let app = app();
let entries = menus::menu_entries(&app);
for lib in ["Brush Libraries", "Symbol Libraries"] {
let items: Vec<_> = entries.iter().filter(|e| e.path == ["Window", lib]).collect();
let labels: Vec<&str> = items.iter().map(|e| e.label.as_str()).collect();
assert_eq!(labels, ["Built-in Libraries", "User Defined", "Other Library…"], "{lib}");
assert!(items.iter().all(|e| !e.enabled && e.command.is_none()), "{lib}");
}
}
#[test]
fn stroke_profiles_have_descriptive_names() {
let labels: Vec<&str> = vectorcraft_doc::WidthProfile::PRESETS.iter().map(|p| p.label).collect();
assert_eq!(labels, ["Uniform", "Lens", "Taper Start", "Taper End", "Pinch", "Teardrop", "Wave"]);
}
#[test]
fn swatches_menu_offers_save_swatch_library() {
let mut app = app();
let text = painted_text(&mut app, crate::panels::swatches::menu);
assert!(text.contains("Save Swatch Library…"), "{text}");
assert!(!text.contains("ASE"), "{text}");
}
#[test]
fn edit_menu_lists_pdf_presets() {
let app = app();
// Implemented (M4.44): enabled, and it opens Edit → PDF Presets.
assert!(
menus::menu_entries(&app)
.iter()
.any(|e| e.path == ["Edit"] && e.label == "PDF Presets…" && e.enabled && e.command.as_deref() == Some("ui.pdfPresetsDialog"))
);
}
#[test]
fn clipboard_preferences_name_the_legacy_format_neutrally() {
let label = |key: &str| vectorcraft_engine::cmd::prefscmds::PREF_SPECS.iter().find(|p| p.key == key).unwrap().label;
assert_eq!(label("copyAicb"), "Legacy vector clipboard (no transparency)");
assert_eq!(label("aicbMode"), "Legacy vector clipboard");
}
#[test]
fn shortcut_set_old_name_is_accepted() {
use crate::shortcut_editor::{PRESETS, import_json};
let (old, _) = crate::shortcut_editor::LEGACY_PRESETS[0];
assert_eq!(PRESETS, ["VectorCraft Defaults", "Classic Defaults"]);
// UI preferences saved by an earlier version load (and save again) under the current name.
let ui: crate::state::UiState = serde_json::from_value(json!({ "shortcut_set": old })).unwrap();
assert_eq!(ui.shortcut_set, "Classic Defaults");
let saved = serde_json::to_value(&ui).unwrap();
assert_eq!(saved["shortcut_set"], "Classic Defaults");
let back: crate::state::UiState = serde_json::from_value(saved).unwrap();
assert_eq!(back.shortcut_set, "Classic Defaults");
let default: crate::state::UiState = serde_json::from_value(json!({})).unwrap();
assert_eq!(default.shortcut_set, PRESETS[0]);
// The command and imported sets take the old name too.
let mut app = app();
app.run("shortcuts.preset", json!({ "name": old })).unwrap();
assert_eq!(app.ui.shortcut_set, "Classic Defaults");
app.run("shortcuts.preset", json!({ "name": "VectorCraft Defaults" })).unwrap();
assert_eq!(app.ui.shortcut_set, "VectorCraft Defaults");
assert!(app.run("shortcuts.preset", json!({ "name": "Nope" })).is_err());
let (set, _) = import_json(&json!({ "set": old, "overrides": {} })).unwrap();
assert_eq!(set, "Classic Defaults");
}