Files
figcraft/crates/ui-egui/src/missing_fonts_tests.rs
T
RealtaNuaandClaude Opus 5.5 a3c5025186 M7: missing fonts window only after a file opens; wait for the font scan (#752)
From review:
- The check ran after any file.open/openRecent/import result, so a
  pasted or dropped image, reopening a file that was already open, or
  Clear recent brought the window back. It now runs only for results
  with a `summary` (a file opened, or pages imported), and never in the
  browser.
- Text > Missing fonts... before the startup scan finished listed
  installed fonts as missing. It now says it is checking the installed
  fonts and lists them once that is done
  (`figcraft_text::installed_fonts_known`, also true when system fonts
  are opted out, where checks used to wait forever).
- ROADMAP progress log and file-format-parity status line.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 22:06:03 +07:00

150 lines
6.2 KiB
Rust

use figcraft_engine::Session;
use serde_json::{Value, json};
use crate::import_report::Report;
use crate::layers_drag_tests::Ui;
/// A saved file with text in `families` (the first two layers share the first family).
fn saved(name: &str, families: &[&str]) -> String {
let mut s = Session::new();
for (i, f) in families.iter().chain(families.first()).enumerate() {
s.execute("text.create", &json!({"text": "Hamburgefonstiv", "x": 0, "y": 40 * i, "props": {"fontFamily": f}})).unwrap();
}
let path = std::env::temp_dir().join(format!("figcraft-missing-fonts-{name}-{}.figcraft", std::process::id()));
let path = path.to_string_lossy().into_owned();
s.execute("file.saveAs", &json!({"path": path})).unwrap();
path
}
/// The installed fonts are indexed, as the desktop app does at startup.
fn fonts_indexed() -> bool {
figcraft_text::scan_system_fonts();
figcraft_text::installed_fonts_known()
}
fn listed(u: &Ui) -> Value {
u.app.inspect()["missingFonts"].clone()
}
#[test]
fn opening_a_file_lists_its_missing_fonts_and_not_the_installed_ones() {
if !fonts_indexed() {
eprintln!("skipped: system fonts opted out");
return;
}
let installed = figcraft_text::family_names().into_iter().next();
let mut families = vec!["FigCraft Missing UI"];
families.extend(installed.as_deref());
let path = saved("open", &families);
let mut u = Ui::new(Session::new());
// Programmatic opens (control channel, MCP, scripts) never open the window.
u.app.run("file.open", json!({"path": path}));
u.frame(vec![]);
assert_eq!(listed(&u), Value::Null);
u.app.run("file.close", json!({}));
u.app.run_user("file.open", json!({"path": path}));
std::fs::remove_file(&path).unwrap();
u.frame(vec![]);
assert_eq!(listed(&u), json!({"families": [{"family": "FigCraft Missing UI", "layers": 2}], "indexing": false}));
u.frame(vec![]);
let shown = u.frame_output(vec![]).shapes;
assert!(crate::import_report_tests::shown_texts(shown).iter().any(|t| t == "2 text layers"));
// It goes with its file.
u.app.run("file.close", json!({}));
u.frame(vec![]);
assert_eq!(listed(&u), Value::Null);
}
#[test]
fn a_file_without_missing_fonts_opens_no_window() {
if !fonts_indexed() {
eprintln!("skipped: system fonts opted out");
return;
}
let Some(installed) = figcraft_text::family_names().into_iter().next() else {
eprintln!("skipped: no fonts on this machine");
return;
};
let path = saved("clean", &[&installed]);
let mut u = Ui::new(Session::new());
u.app.run_user("file.open", json!({"path": path}));
std::fs::remove_file(&path).unwrap();
u.frame(vec![]);
assert_eq!(listed(&u), Value::Null);
assert!(u.app.ui.font_checks.is_empty(), "the check ran");
}
#[test]
fn it_waits_for_the_import_report_to_close() {
if !fonts_indexed() {
eprintln!("skipped: system fonts opted out");
return;
}
let path = saved("report", &["FigCraft Missing Report"]);
let mut u = Ui::new(Session::new());
u.app.run_user("file.open", json!({"path": path}));
std::fs::remove_file(&path).unwrap();
let mut report =
Report::from_result("file.open", &json!({}), &json!({"warnings": ["Synthetic note"], "summary": {"name": "Synthetic"}})).unwrap();
report.uid = u.app.session.ds().unwrap().uid;
u.app.ui.import_report = Some(report);
u.frame(vec![]);
assert_eq!(listed(&u), Value::Null, "the import report comes first");
u.app.ui.import_report = None;
u.frame(vec![]);
assert_eq!(listed(&u)["families"][0]["family"], "FigCraft Missing Report");
}
#[test]
fn text_menu_shows_the_window_for_the_file_in_front_even_with_nothing_missing() {
assert!(fonts_indexed());
let mut u = Ui::new(Session::new());
assert!(u.app.run("text.missingFonts", json!({})).is_some());
assert_eq!(listed(&u), json!({"families": [], "indexing": false}));
u.frame(vec![]);
let shown = u.frame_output(vec![]).shapes;
assert!(crate::import_report_tests::shown_texts(shown).iter().any(|t| t == "All the fonts this file uses are installed"));
let escape =
vec![egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::NONE }];
u.frame(escape);
assert_eq!(listed(&u), Value::Null, "Escape closes it");
}
#[test]
fn asked_for_before_the_fonts_are_indexed_it_lists_them_once_they_are() {
assert!(fonts_indexed());
let path = saved("indexing", &["FigCraft Missing Indexing"]);
let mut u = Ui::new(Session::new());
u.app.run("file.open", json!({"path": path}));
std::fs::remove_file(&path).unwrap();
// As Text ▸ Missing fonts leaves it while the startup scan is still running.
let uid = u.app.session.ds().unwrap().uid;
u.app.ui.missing_fonts = Some(crate::missing_fonts::Shown { uid, families: Vec::new(), indexing: true });
u.frame(vec![]);
assert_eq!(listed(&u), json!({"families": [{"family": "FigCraft Missing Indexing", "layers": 2}], "indexing": false}));
}
#[test]
fn placing_images_or_clearing_recents_does_not_bring_it_back() {
assert!(fonts_indexed());
let path = saved("place", &["FigCraft Missing Place"]);
let svg = std::env::temp_dir().join(format!("figcraft-missing-fonts-place-{}.svg", std::process::id()));
std::fs::write(&svg, r#"<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><rect width="10" height="10"/></svg>"#).unwrap();
let mut u = Ui::new(Session::new());
u.app.run_user("file.open", json!({"path": path}));
u.frame(vec![]);
assert_eq!(listed(&u)["families"][0]["family"], "FigCraft Missing Place");
u.app.ui.missing_fonts = None;
// A placed SVG, the same file opened again (it only comes to the front), Clear recent.
assert!(u.app.run_user("file.import", json!({"path": svg.to_string_lossy()})).is_some());
assert!(u.app.run_user("file.open", json!({"path": path})).is_some());
u.app.run_user("file.openRecent", json!({"clear": true}));
std::fs::remove_file(&path).unwrap();
std::fs::remove_file(&svg).unwrap();
u.frame(vec![]);
assert!(u.app.ui.font_checks.is_empty());
assert_eq!(listed(&u), Value::Null);
}