refactor(ui-egui): move prefs_dialog.rs tests to prefs_dialog/tests.rs

This commit is contained in:
aiongg
2026-10-26 18:18:17 +00:00
parent 212cef5787
commit 04b9425f5e
2 changed files with 376 additions and 377 deletions
+1 -377
View File
@@ -418,380 +418,4 @@ fn labeled(ui: &mut egui::Ui, label: &str, add: impl FnOnce(&mut egui::Ui)) {
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Services;
use vectorcraft_engine::Session;
#[test]
fn geometric_snapping_is_a_saved_smart_guides_preference() {
let mut a = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
open(&mut a, Some("Smart Guides"));
let text = crate::tests_labels::painted_text(&mut a, |a, ui| show(a, ui.ctx()));
assert!(text.contains("Geometric Guides") && text.contains("Snap to Perpendicular") && text.contains("Snap to Tangent"), "{text}");
a.ui.dialog.as_mut().unwrap().fields.insert("snapPerpendicular".into(), json!(false));
confirm(&mut a).unwrap();
assert!(!a.session.prefs.snap_perpendicular && a.session.prefs.snap_tangent);
let saved: Prefs = serde_json::from_value(a.ui.engine_prefs.clone()).unwrap();
assert!(!saved.snap_perpendicular && saved.snap_tangent);
assert!(a.run("view.snapping", json!({})).is_err());
}
/// A resting checkbox must have a visible outline in both states and every brightness.
#[test]
fn preference_checkboxes_draw_outlines_without_hover() {
for brightness in Brightness::ALL {
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
theme::apply(&ctx, brightness);
for checked in [false, true] {
let mut d = Dialog::new("preferences", json!({"showToolTips": checked}));
let raw =
egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(400.0, 200.0))), ..Default::default() };
let mut out = ctx.run_ui(raw, |ui| bool_row(ui, &mut d, "showToolTips", "Show Tool Tips"));
out.textures_delta.clear();
let outlined = out.shapes.iter().any(|shape| match &shape.shape {
egui::Shape::Rect(r) => r.stroke.width > 0.0 && r.rect.width() <= 24.0 && r.rect.height() <= 24.0,
_ => false,
});
assert!(outlined, "{brightness:?}, checked={checked}: no checkbox outline");
}
}
}
/// User Interface › Canvas Color (#663): White and the three greys paint the canvas around the
/// artboards; Match User Interface Brightness keeps the theme's.
#[test]
fn canvas_color_choices_paint_the_pasteboard() {
let mut app = VectorcraftApp::new(Session::new(), Services::default());
let ctx = egui::Context::default();
apply_runtime(&mut app, &ctx);
let themed = Tokens::get(&ctx).pasteboard;
for (key, want) in [
("white", egui::Color32::WHITE),
("lightGray", egui::Color32::from_gray(0xc8)),
("mediumGray", egui::Color32::from_gray(0x96)),
("darkGray", egui::Color32::from_gray(0x5a)),
("matchUi", themed),
] {
app.run("prefs.set", serde_json::json!({"key": "canvasColor", "value": key})).unwrap();
apply_runtime(&mut app, &ctx);
assert_eq!(Tokens::get(&ctx).pasteboard, want, "{key}");
}
assert!(app.run("prefs.set", serde_json::json!({"key": "canvasColor", "value": "pink"})).is_err());
}
fn app() -> VectorcraftApp {
VectorcraftApp::new(Session::new(), Services::default())
}
#[test]
fn dialog_ok_applies_prefs_and_ui_fields() {
let mut a = app();
open(&mut a, Some("general"));
let d = a.ui.dialog.as_mut().unwrap();
assert_eq!(d.str("__category"), "General");
d.fields.insert("keyboardIncrement".into(), json!(4));
d.fields.insert("uiBrightness".into(), json!("light"));
d.fields.insert("__smartGuides".into(), json!(false));
confirm(&mut a).unwrap();
assert!(a.ui.dialog.is_none());
assert_eq!(a.session.prefs.keyboard_increment, 4.0);
assert_eq!(a.session.prefs.ui_brightness, "light");
assert!(!a.ui.view.smart_guides);
assert_eq!(a.ui.engine_prefs["keyboardIncrement"], json!(4.0));
}
/// General › Show Tool Tips: off, a button held under the pointer shows no tool tip; back on,
/// it does again (#394).
#[test]
fn show_tool_tips_off_hides_tool_tips() {
fn texts(s: &egui::Shape, out: &mut Vec<String>) {
match s {
egui::Shape::Text(t) => out.push(t.galley.text().to_string()),
egui::Shape::Vec(v) => v.iter().for_each(|s| texts(s, out)),
_ => {}
}
}
let mut a = app();
let ctx = egui::Context::default();
let mut time = 0.0;
// Come from elsewhere and hover the button for two seconds: did its tool tip show?
let mut hover = |a: &mut VectorcraftApp| {
let mut shown = vec![];
for i in 0..20 {
time += 0.1;
let events = match i {
0 => vec![egui::Event::PointerGone],
1 => vec![egui::Event::PointerMoved(egui::pos2(20.0, 12.0))],
_ => vec![],
};
let raw = egui::RawInput {
events,
time: Some(time),
screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(400.0, 300.0))),
..Default::default()
};
let mut out = ctx.run_ui(raw, |ui| {
apply_runtime(a, ui.ctx());
ui.button("Button").on_hover_text("The tool tip");
});
out.textures_delta.clear();
out.shapes.iter().for_each(|c| texts(&c.shape, &mut shown));
}
shown.iter().any(|t| t == "The tool tip")
};
assert!(hover(&mut a), "on by default");
a.run("prefs.set", json!({"key": "showToolTips", "value": false})).unwrap();
assert!(!hover(&mut a), "off: no tool tip");
a.run("prefs.set", json!({"key": "showToolTips", "value": true})).unwrap();
assert!(hover(&mut a), "on again");
}
#[test]
fn dialog_ok_with_invalid_value_keeps_dialog_and_prefs() {
let mut a = app();
open(&mut a, None);
a.ui.dialog.as_mut().unwrap().fields.insert("anchorSize".into(), json!(42));
assert!(confirm(&mut a).is_err());
assert!(a.ui.dialog.is_some());
assert_eq!(a.session.prefs, Prefs::default());
}
#[test]
fn prefs_persist_through_ui_state() {
let mut a = app();
a.run("prefs.set", json!({"key": "gridlineEvery", "value": 50})).unwrap();
snapshot(&mut a);
let saved = serde_json::to_vec(&a.ui).unwrap();
let mut b = app();
b.ui = serde_json::from_slice::<crate::UiState>(&saved).unwrap().sanitized();
restore(&mut b);
assert_eq!(b.session.prefs.gridline_every, 50.0);
}
#[test]
fn old_pref_files_keep_their_brightness() {
let mut a = app();
a.ui.brightness = Brightness::Light;
a.ui.engine_prefs = Value::Null;
restore(&mut a);
assert_eq!(a.session.prefs.ui_brightness, "light");
}
/// Performance › Graphics Processor (#306, #502): shown with its restart note, applied by OK
/// and saved with the UI state under the key the desktop app reads before the window opens.
#[test]
fn graphics_processor_preference_shows_and_persists() {
fn texts(s: &egui::Shape, out: &mut Vec<String>) {
match s {
egui::Shape::Text(t) => out.push(t.galley.text().to_string()),
egui::Shape::Vec(v) => v.iter().for_each(|s| texts(s, out)),
_ => {}
}
}
let mut a = app();
open(&mut a, Some("Performance"));
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
// The window sizes itself on the first frame and draws on the second.
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut shown = vec![];
out.shapes.iter().for_each(|c| texts(&c.shape, &mut shown));
assert!(shown.iter().any(|t| t.starts_with("Graphics Processor")), "{shown:?}");
assert!(shown.iter().any(|t| t == "Automatic"), "{shown:?}");
assert!(shown.iter().any(|t| t == "Applies the next time VectorCraft starts."), "{shown:?}");
a.ui.dialog.as_mut().unwrap().fields.insert("gpuPreference".into(), json!("highPerformance"));
assert!(!a.take_prefs_changed(), "nothing to write before OK");
confirm(&mut a).unwrap();
assert!(a.take_prefs_changed(), "OK asks the host to write the preferences file");
assert!(!a.take_prefs_changed());
assert_eq!(a.session.prefs.gpu_preference, "highPerformance");
let saved: Value = serde_json::from_slice(&serde_json::to_vec(&a.ui).unwrap()).unwrap();
assert_eq!(saved["engine_prefs"]["gpuPreference"], json!("highPerformance"));
}
/// User Interface › System Title Bar: shown with its next-launch note, applied by OK and
/// saved with the UI state under the key the desktop app reads before the window opens.
#[test]
fn system_title_bar_preference_shows_and_persists() {
fn texts(s: &egui::Shape, out: &mut Vec<String>) {
match s {
egui::Shape::Text(t) => out.push(t.galley.text().to_string()),
egui::Shape::Vec(v) => v.iter().for_each(|s| texts(s, out)),
_ => {}
}
}
let mut a = app();
open(&mut a, Some("User Interface"));
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
// The window sizes itself on the first frame and draws on the second.
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut shown = vec![];
out.shapes.iter().for_each(|c| texts(&c.shape, &mut shown));
let supported = cfg!(all(not(target_arch = "wasm32"), not(target_os = "macos")));
assert_eq!(shown.iter().any(|t| t == "System Title Bar"), supported, "{shown:?}");
assert_eq!(shown.iter().any(|t| t == "Applies the next time VectorCraft starts."), supported, "{shown:?}");
a.ui.dialog.as_mut().unwrap().fields.insert("systemTitleBar".into(), json!(true));
confirm(&mut a).unwrap();
assert!(a.session.prefs.system_title_bar);
let saved: Value = serde_json::from_slice(&serde_json::to_vec(&a.ui).unwrap()).unwrap();
assert_eq!(saved["engine_prefs"]["systemTitleBar"], json!(true));
}
/// Performance › Linux Display Server (#2028): on Linux it shows the drawing-tablet note, and
/// X11 (XWayland) When a Pen Is Attached is applied by OK and saved under the key the desktop
/// app reads before the window opens.
#[test]
fn linux_display_server_offers_the_pen_choice() {
fn texts(s: &egui::Shape, out: &mut Vec<String>) {
match s {
egui::Shape::Text(t) => out.push(t.galley.text().to_string()),
egui::Shape::Vec(v) => v.iter().for_each(|s| texts(s, out)),
_ => {}
}
}
let mut a = app();
open(&mut a, Some("Performance"));
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
// The window sizes itself on the first frame and draws on the second.
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut shown = vec![];
out.shapes.iter().for_each(|c| texts(&c.shape, &mut shown));
let linux = cfg!(target_os = "linux");
assert_eq!(shown.iter().any(|t| t.starts_with("Linux Display Server")), linux, "{shown:?}");
let note = "If a drawing tablet's pen doesn't draw, choose an X11 (XWayland) option.";
assert_eq!(shown.iter().any(|t| t == note), linux, "{shown:?}");
a.ui.dialog.as_mut().unwrap().fields.insert("linuxDisplayServer".into(), json!("x11WithPen"));
confirm(&mut a).unwrap();
assert_eq!(a.session.prefs.linux_display_server, "x11WithPen");
let saved: Value = serde_json::from_slice(&serde_json::to_vec(&a.ui).unwrap()).unwrap();
assert_eq!(saved["engine_prefs"]["linuxDisplayServer"], json!("x11WithPen"));
}
/// #1866: the category shown is highlighted in the list while the pointer is elsewhere.
#[test]
fn the_category_shown_is_highlighted() {
fn rects(s: &egui::Shape, out: &mut Vec<(egui::Rect, egui::Color32)>) {
match s {
egui::Shape::Rect(r) => out.push((r.rect, r.fill)),
egui::Shape::Vec(v) => v.iter().for_each(|s| rects(s, out)),
_ => {}
}
}
let mut a = app();
open(&mut a, Some("Units"));
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
// The window sizes itself on the first frame and draws on the second.
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut shown = vec![];
out.shapes.iter().for_each(|c| rects(&c.shape, &mut shown));
// The list's rows, top to bottom: only the fourth (Units) is filled.
let rows: Vec<_> = shown.iter().filter(|(r, _)| r.width() == 184.0 && r.height() < 30.0).collect();
assert_eq!(rows.len(), PREF_CATEGORIES.len(), "{shown:?}");
let filled: Vec<usize> = rows.iter().enumerate().filter(|(_, (_, c))| c.a() > 0).map(|(i, _)| i).collect();
assert_eq!(filled, vec![3], "{rows:?}");
}
/// Units › Numbers Without Units Are Points (#394): the preference reaches the fields when it
/// changes, and its checkbox is dimmed unless a unit is Picas.
#[test]
fn numbers_without_units_are_points_reaches_the_fields_and_dims_without_picas() {
let mut a = app();
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let frame = |a: &mut VectorcraftApp| {
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| {
apply_runtime(a, ui.ctx());
show(a, ui.ctx());
});
out.textures_delta.clear();
};
let read = |ctx: &egui::Context| widgets::typed_unit(ctx, vectorcraft_doc::Unit::Picas);
frame(&mut a);
assert_eq!(read(&ctx), vectorcraft_doc::Unit::Points, "on by default");
a.run("prefs.set", json!({"key": "numbersWithoutUnitsArePoints", "value": false})).unwrap();
frame(&mut a);
assert_eq!(read(&ctx), vectorcraft_doc::Unit::Picas, "off");
// The checkbox: dimmed with every unit in points, enabled once a unit is Picas.
open(&mut a, Some("Units"));
let d = a.ui.dialog.as_mut().unwrap();
assert!(!picas_in_use(d), "dimmed in points");
d.fields.insert("unitsStroke".into(), json!("picas"));
assert!(picas_in_use(d), "enabled with a unit in picas");
}
/// 0.5.0 saved `powerSaving` for everyone (its default): it reads as Automatic, while the
/// choices this version offers are kept (#502).
#[test]
fn a_graphics_processor_this_version_does_not_offer_reads_as_automatic() {
for (saved, read) in [("powerSaving", "automatic"), ("turbo", "automatic"), ("lowPower", "lowPower"), ("highPerformance", "highPerformance")]
{
let mut a = app();
a.ui.engine_prefs = json!({"gpuPreference": saved});
restore(&mut a);
assert_eq!(a.session.prefs.gpu_preference, read, "{saved}");
}
}
#[test]
fn every_category_renders_fields() {
for c in PREF_CATEGORIES {
assert!(PREF_SPECS.iter().any(|s| s.category == *c));
}
}
/// Preferences › Type › Show Font Names in English (#394): OK on the dialog stores the flag the
/// font menus read (Character panel, Type → Font).
#[test]
fn show_font_names_in_english_dialog_ok_stores_the_flag() {
let mut a = app();
assert!(a.session.prefs.font_names_in_english);
open(&mut a, Some("Type"));
assert_eq!(a.ui.dialog.as_ref().unwrap().str("__category"), "Type");
a.ui.dialog.as_mut().unwrap().fields.insert("fontNamesInEnglish".into(), json!(false));
confirm(&mut a).unwrap();
assert!(a.ui.dialog.is_none());
assert!(!a.session.prefs.font_names_in_english);
assert!(!crate::font_menu::MenuLook::of(&a).english_names);
}
/// Preferences › Hyphenation › Exceptions (#394): OK on the dialog pushes the list into the
/// hyphenator the same way `prefs.set` does (no need to drive the live Preferences window).
#[test]
fn hyphenation_exceptions_dialog_ok_reaches_the_hyphenator() {
struct Clear;
impl Drop for Clear {
fn drop(&mut self) {
vectorcraft_text::set_hyphenation_exceptions("");
}
}
let _clear = Clear;
let mut a = app();
open(&mut a, Some("Hyphenation"));
let d = a.ui.dialog.as_mut().unwrap();
assert_eq!(d.str("__category"), "Hyphenation");
d.fields.insert("hyphenationExceptions".into(), json!("typography, hap-pen"));
confirm(&mut a).unwrap();
assert!(a.ui.dialog.is_none());
assert_eq!(a.session.prefs.hyphenation_exceptions, "typography, hap-pen");
assert_eq!(vectorcraft_text::hyphenation_exceptions(), "typography, hap-pen");
assert!(vectorcraft_text::hyphen::hyphen_points("typography").is_empty());
assert_eq!(vectorcraft_text::hyphen::hyphen_points("happen"), vec![3]);
}
}
mod tests;
+375
View File
@@ -0,0 +1,375 @@
use super::*;
use crate::Services;
use vectorcraft_engine::Session;
#[test]
fn geometric_snapping_is_a_saved_smart_guides_preference() {
let mut a = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
open(&mut a, Some("Smart Guides"));
let text = crate::tests_labels::painted_text(&mut a, |a, ui| show(a, ui.ctx()));
assert!(text.contains("Geometric Guides") && text.contains("Snap to Perpendicular") && text.contains("Snap to Tangent"), "{text}");
a.ui.dialog.as_mut().unwrap().fields.insert("snapPerpendicular".into(), json!(false));
confirm(&mut a).unwrap();
assert!(!a.session.prefs.snap_perpendicular && a.session.prefs.snap_tangent);
let saved: Prefs = serde_json::from_value(a.ui.engine_prefs.clone()).unwrap();
assert!(!saved.snap_perpendicular && saved.snap_tangent);
assert!(a.run("view.snapping", json!({})).is_err());
}
/// A resting checkbox must have a visible outline in both states and every brightness.
#[test]
fn preference_checkboxes_draw_outlines_without_hover() {
for brightness in Brightness::ALL {
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
theme::apply(&ctx, brightness);
for checked in [false, true] {
let mut d = Dialog::new("preferences", json!({"showToolTips": checked}));
let raw =
egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(400.0, 200.0))), ..Default::default() };
let mut out = ctx.run_ui(raw, |ui| bool_row(ui, &mut d, "showToolTips", "Show Tool Tips"));
out.textures_delta.clear();
let outlined = out.shapes.iter().any(|shape| match &shape.shape {
egui::Shape::Rect(r) => r.stroke.width > 0.0 && r.rect.width() <= 24.0 && r.rect.height() <= 24.0,
_ => false,
});
assert!(outlined, "{brightness:?}, checked={checked}: no checkbox outline");
}
}
}
/// User Interface › Canvas Color (#663): White and the three greys paint the canvas around the
/// artboards; Match User Interface Brightness keeps the theme's.
#[test]
fn canvas_color_choices_paint_the_pasteboard() {
let mut app = VectorcraftApp::new(Session::new(), Services::default());
let ctx = egui::Context::default();
apply_runtime(&mut app, &ctx);
let themed = Tokens::get(&ctx).pasteboard;
for (key, want) in [
("white", egui::Color32::WHITE),
("lightGray", egui::Color32::from_gray(0xc8)),
("mediumGray", egui::Color32::from_gray(0x96)),
("darkGray", egui::Color32::from_gray(0x5a)),
("matchUi", themed),
] {
app.run("prefs.set", serde_json::json!({"key": "canvasColor", "value": key})).unwrap();
apply_runtime(&mut app, &ctx);
assert_eq!(Tokens::get(&ctx).pasteboard, want, "{key}");
}
assert!(app.run("prefs.set", serde_json::json!({"key": "canvasColor", "value": "pink"})).is_err());
}
fn app() -> VectorcraftApp {
VectorcraftApp::new(Session::new(), Services::default())
}
#[test]
fn dialog_ok_applies_prefs_and_ui_fields() {
let mut a = app();
open(&mut a, Some("general"));
let d = a.ui.dialog.as_mut().unwrap();
assert_eq!(d.str("__category"), "General");
d.fields.insert("keyboardIncrement".into(), json!(4));
d.fields.insert("uiBrightness".into(), json!("light"));
d.fields.insert("__smartGuides".into(), json!(false));
confirm(&mut a).unwrap();
assert!(a.ui.dialog.is_none());
assert_eq!(a.session.prefs.keyboard_increment, 4.0);
assert_eq!(a.session.prefs.ui_brightness, "light");
assert!(!a.ui.view.smart_guides);
assert_eq!(a.ui.engine_prefs["keyboardIncrement"], json!(4.0));
}
/// General › Show Tool Tips: off, a button held under the pointer shows no tool tip; back on,
/// it does again (#394).
#[test]
fn show_tool_tips_off_hides_tool_tips() {
fn texts(s: &egui::Shape, out: &mut Vec<String>) {
match s {
egui::Shape::Text(t) => out.push(t.galley.text().to_string()),
egui::Shape::Vec(v) => v.iter().for_each(|s| texts(s, out)),
_ => {}
}
}
let mut a = app();
let ctx = egui::Context::default();
let mut time = 0.0;
// Come from elsewhere and hover the button for two seconds: did its tool tip show?
let mut hover = |a: &mut VectorcraftApp| {
let mut shown = vec![];
for i in 0..20 {
time += 0.1;
let events = match i {
0 => vec![egui::Event::PointerGone],
1 => vec![egui::Event::PointerMoved(egui::pos2(20.0, 12.0))],
_ => vec![],
};
let raw = egui::RawInput {
events,
time: Some(time),
screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(400.0, 300.0))),
..Default::default()
};
let mut out = ctx.run_ui(raw, |ui| {
apply_runtime(a, ui.ctx());
ui.button("Button").on_hover_text("The tool tip");
});
out.textures_delta.clear();
out.shapes.iter().for_each(|c| texts(&c.shape, &mut shown));
}
shown.iter().any(|t| t == "The tool tip")
};
assert!(hover(&mut a), "on by default");
a.run("prefs.set", json!({"key": "showToolTips", "value": false})).unwrap();
assert!(!hover(&mut a), "off: no tool tip");
a.run("prefs.set", json!({"key": "showToolTips", "value": true})).unwrap();
assert!(hover(&mut a), "on again");
}
#[test]
fn dialog_ok_with_invalid_value_keeps_dialog_and_prefs() {
let mut a = app();
open(&mut a, None);
a.ui.dialog.as_mut().unwrap().fields.insert("anchorSize".into(), json!(42));
assert!(confirm(&mut a).is_err());
assert!(a.ui.dialog.is_some());
assert_eq!(a.session.prefs, Prefs::default());
}
#[test]
fn prefs_persist_through_ui_state() {
let mut a = app();
a.run("prefs.set", json!({"key": "gridlineEvery", "value": 50})).unwrap();
snapshot(&mut a);
let saved = serde_json::to_vec(&a.ui).unwrap();
let mut b = app();
b.ui = serde_json::from_slice::<crate::UiState>(&saved).unwrap().sanitized();
restore(&mut b);
assert_eq!(b.session.prefs.gridline_every, 50.0);
}
#[test]
fn old_pref_files_keep_their_brightness() {
let mut a = app();
a.ui.brightness = Brightness::Light;
a.ui.engine_prefs = Value::Null;
restore(&mut a);
assert_eq!(a.session.prefs.ui_brightness, "light");
}
/// Performance › Graphics Processor (#306, #502): shown with its restart note, applied by OK
/// and saved with the UI state under the key the desktop app reads before the window opens.
#[test]
fn graphics_processor_preference_shows_and_persists() {
fn texts(s: &egui::Shape, out: &mut Vec<String>) {
match s {
egui::Shape::Text(t) => out.push(t.galley.text().to_string()),
egui::Shape::Vec(v) => v.iter().for_each(|s| texts(s, out)),
_ => {}
}
}
let mut a = app();
open(&mut a, Some("Performance"));
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
// The window sizes itself on the first frame and draws on the second.
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut shown = vec![];
out.shapes.iter().for_each(|c| texts(&c.shape, &mut shown));
assert!(shown.iter().any(|t| t.starts_with("Graphics Processor")), "{shown:?}");
assert!(shown.iter().any(|t| t == "Automatic"), "{shown:?}");
assert!(shown.iter().any(|t| t == "Applies the next time VectorCraft starts."), "{shown:?}");
a.ui.dialog.as_mut().unwrap().fields.insert("gpuPreference".into(), json!("highPerformance"));
assert!(!a.take_prefs_changed(), "nothing to write before OK");
confirm(&mut a).unwrap();
assert!(a.take_prefs_changed(), "OK asks the host to write the preferences file");
assert!(!a.take_prefs_changed());
assert_eq!(a.session.prefs.gpu_preference, "highPerformance");
let saved: Value = serde_json::from_slice(&serde_json::to_vec(&a.ui).unwrap()).unwrap();
assert_eq!(saved["engine_prefs"]["gpuPreference"], json!("highPerformance"));
}
/// User Interface › System Title Bar: shown with its next-launch note, applied by OK and
/// saved with the UI state under the key the desktop app reads before the window opens.
#[test]
fn system_title_bar_preference_shows_and_persists() {
fn texts(s: &egui::Shape, out: &mut Vec<String>) {
match s {
egui::Shape::Text(t) => out.push(t.galley.text().to_string()),
egui::Shape::Vec(v) => v.iter().for_each(|s| texts(s, out)),
_ => {}
}
}
let mut a = app();
open(&mut a, Some("User Interface"));
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
// The window sizes itself on the first frame and draws on the second.
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut shown = vec![];
out.shapes.iter().for_each(|c| texts(&c.shape, &mut shown));
let supported = cfg!(all(not(target_arch = "wasm32"), not(target_os = "macos")));
assert_eq!(shown.iter().any(|t| t == "System Title Bar"), supported, "{shown:?}");
assert_eq!(shown.iter().any(|t| t == "Applies the next time VectorCraft starts."), supported, "{shown:?}");
a.ui.dialog.as_mut().unwrap().fields.insert("systemTitleBar".into(), json!(true));
confirm(&mut a).unwrap();
assert!(a.session.prefs.system_title_bar);
let saved: Value = serde_json::from_slice(&serde_json::to_vec(&a.ui).unwrap()).unwrap();
assert_eq!(saved["engine_prefs"]["systemTitleBar"], json!(true));
}
/// Performance › Linux Display Server (#2028): on Linux it shows the drawing-tablet note, and
/// X11 (XWayland) When a Pen Is Attached is applied by OK and saved under the key the desktop
/// app reads before the window opens.
#[test]
fn linux_display_server_offers_the_pen_choice() {
fn texts(s: &egui::Shape, out: &mut Vec<String>) {
match s {
egui::Shape::Text(t) => out.push(t.galley.text().to_string()),
egui::Shape::Vec(v) => v.iter().for_each(|s| texts(s, out)),
_ => {}
}
}
let mut a = app();
open(&mut a, Some("Performance"));
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
// The window sizes itself on the first frame and draws on the second.
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut shown = vec![];
out.shapes.iter().for_each(|c| texts(&c.shape, &mut shown));
let linux = cfg!(target_os = "linux");
assert_eq!(shown.iter().any(|t| t.starts_with("Linux Display Server")), linux, "{shown:?}");
let note = "If a drawing tablet's pen doesn't draw, choose an X11 (XWayland) option.";
assert_eq!(shown.iter().any(|t| t == note), linux, "{shown:?}");
a.ui.dialog.as_mut().unwrap().fields.insert("linuxDisplayServer".into(), json!("x11WithPen"));
confirm(&mut a).unwrap();
assert_eq!(a.session.prefs.linux_display_server, "x11WithPen");
let saved: Value = serde_json::from_slice(&serde_json::to_vec(&a.ui).unwrap()).unwrap();
assert_eq!(saved["engine_prefs"]["linuxDisplayServer"], json!("x11WithPen"));
}
/// #1866: the category shown is highlighted in the list while the pointer is elsewhere.
#[test]
fn the_category_shown_is_highlighted() {
fn rects(s: &egui::Shape, out: &mut Vec<(egui::Rect, egui::Color32)>) {
match s {
egui::Shape::Rect(r) => out.push((r.rect, r.fill)),
egui::Shape::Vec(v) => v.iter().for_each(|s| rects(s, out)),
_ => {}
}
}
let mut a = app();
open(&mut a, Some("Units"));
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
// The window sizes itself on the first frame and draws on the second.
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx()));
out.textures_delta.clear();
let mut shown = vec![];
out.shapes.iter().for_each(|c| rects(&c.shape, &mut shown));
// The list's rows, top to bottom: only the fourth (Units) is filled.
let rows: Vec<_> = shown.iter().filter(|(r, _)| r.width() == 184.0 && r.height() < 30.0).collect();
assert_eq!(rows.len(), PREF_CATEGORIES.len(), "{shown:?}");
let filled: Vec<usize> = rows.iter().enumerate().filter(|(_, (_, c))| c.a() > 0).map(|(i, _)| i).collect();
assert_eq!(filled, vec![3], "{rows:?}");
}
/// Units › Numbers Without Units Are Points (#394): the preference reaches the fields when it
/// changes, and its checkbox is dimmed unless a unit is Picas.
#[test]
fn numbers_without_units_are_points_reaches_the_fields_and_dims_without_picas() {
let mut a = app();
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let frame = |a: &mut VectorcraftApp| {
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| {
apply_runtime(a, ui.ctx());
show(a, ui.ctx());
});
out.textures_delta.clear();
};
let read = |ctx: &egui::Context| widgets::typed_unit(ctx, vectorcraft_doc::Unit::Picas);
frame(&mut a);
assert_eq!(read(&ctx), vectorcraft_doc::Unit::Points, "on by default");
a.run("prefs.set", json!({"key": "numbersWithoutUnitsArePoints", "value": false})).unwrap();
frame(&mut a);
assert_eq!(read(&ctx), vectorcraft_doc::Unit::Picas, "off");
// The checkbox: dimmed with every unit in points, enabled once a unit is Picas.
open(&mut a, Some("Units"));
let d = a.ui.dialog.as_mut().unwrap();
assert!(!picas_in_use(d), "dimmed in points");
d.fields.insert("unitsStroke".into(), json!("picas"));
assert!(picas_in_use(d), "enabled with a unit in picas");
}
/// 0.5.0 saved `powerSaving` for everyone (its default): it reads as Automatic, while the
/// choices this version offers are kept (#502).
#[test]
fn a_graphics_processor_this_version_does_not_offer_reads_as_automatic() {
for (saved, read) in [("powerSaving", "automatic"), ("turbo", "automatic"), ("lowPower", "lowPower"), ("highPerformance", "highPerformance")]
{
let mut a = app();
a.ui.engine_prefs = json!({"gpuPreference": saved});
restore(&mut a);
assert_eq!(a.session.prefs.gpu_preference, read, "{saved}");
}
}
#[test]
fn every_category_renders_fields() {
for c in PREF_CATEGORIES {
assert!(PREF_SPECS.iter().any(|s| s.category == *c));
}
}
/// Preferences › Type › Show Font Names in English (#394): OK on the dialog stores the flag the
/// font menus read (Character panel, Type → Font).
#[test]
fn show_font_names_in_english_dialog_ok_stores_the_flag() {
let mut a = app();
assert!(a.session.prefs.font_names_in_english);
open(&mut a, Some("Type"));
assert_eq!(a.ui.dialog.as_ref().unwrap().str("__category"), "Type");
a.ui.dialog.as_mut().unwrap().fields.insert("fontNamesInEnglish".into(), json!(false));
confirm(&mut a).unwrap();
assert!(a.ui.dialog.is_none());
assert!(!a.session.prefs.font_names_in_english);
assert!(!crate::font_menu::MenuLook::of(&a).english_names);
}
/// Preferences › Hyphenation › Exceptions (#394): OK on the dialog pushes the list into the
/// hyphenator the same way `prefs.set` does (no need to drive the live Preferences window).
#[test]
fn hyphenation_exceptions_dialog_ok_reaches_the_hyphenator() {
struct Clear;
impl Drop for Clear {
fn drop(&mut self) {
vectorcraft_text::set_hyphenation_exceptions("");
}
}
let _clear = Clear;
let mut a = app();
open(&mut a, Some("Hyphenation"));
let d = a.ui.dialog.as_mut().unwrap();
assert_eq!(d.str("__category"), "Hyphenation");
d.fields.insert("hyphenationExceptions".into(), json!("typography, hap-pen"));
confirm(&mut a).unwrap();
assert!(a.ui.dialog.is_none());
assert_eq!(a.session.prefs.hyphenation_exceptions, "typography, hap-pen");
assert_eq!(vectorcraft_text::hyphenation_exceptions(), "typography, hap-pen");
assert!(vectorcraft_text::hyphen::hyphen_points("typography").is_empty());
assert_eq!(vectorcraft_text::hyphen::hyphen_points("happen"), vec![3]);
}