mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-25 19:06:47 -06:00
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>
150 lines
6.2 KiB
Rust
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);
|
|
}
|