Files
vectorcraft/crates/engine/tests/font_folder.rs
T
dotCooCoo c35d5f5b34 Read roots: links.findFiles and image.info follow the configured read roots
With automation read roots in force, links.findFiles and text.findFontFiles
replies, including later polls and stops of a search another client started,
leave out the files found outside the caller's read roots, a sought link's
saved path outside them and the folder searched when it is outside them. A
failed search then gives its reason without the path. The rows keep their
names, ids and status, and the counts stay.

image.info gives link: null for a linked file outside the read roots, as
links.info does. text.addFontFiles gives folder: null when the Fonts folder
is outside the read roots and names the copies there by their file names;
the fonts are still copied.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-12 01:03:41 -08:00

90 lines
5.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! VectorCraft's own Fonts folder: a missing font's file, found in a folder, is copied into it
//! without replacing a file there, and type set in the font redraws in it. One test in its own
//! process, so the process-wide font database and Fonts folder start fresh, as when the app starts.
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::time::{Duration, Instant};
use serde_json::{Value, json};
use vectorcraft_engine::Session;
use vectorcraft_engine::cmd::findfiles::{Rules, plain};
/// How long the test waits for a search to end. The wait only ends the test if the search never
/// ends. A machine busy with other builds can hold a walker for seconds. It is shorter than a search's
/// default `maxSeconds` of 60, which would end a search that does not end by itself.
const WAIT: Duration = Duration::from_secs(30);
/// `text.findFontFiles {}` once the search ended ([`WAIT`] at most).
fn finished(s: &mut Session) -> Value {
let until = Instant::now() + WAIT;
loop {
let r = s.execute("text.findFontFiles", &json!({})).unwrap();
if r["state"] != "searching" || Instant::now() > until {
return r;
}
std::thread::sleep(Duration::from_millis(5));
}
}
#[test]
fn a_found_font_is_copied_without_replacing_a_file_and_its_type_redraws() {
let root = vectorcraft_testkit::temp_dir("font-folder");
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(root.join("Downloads/Deep")).unwrap();
let root = plain(std::fs::canonicalize(&root).unwrap());
let fonts = root.join("App Fonts");
vectorcraft_text::set_app_font_dir(fonts.clone());
assert!(vectorcraft_text::system_font_dirs().contains(&fonts));
let file = root.join("Downloads").join("Deep").join("Findme.otf");
std::fs::write(&file, vectorcraft_testkit::fonts::renamed("Findme Sans 3")).unwrap();
let serif = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../assets/fonts/SourceSerif4-Regular.ttf");
std::fs::copy(serif, root.join("other.ttf")).unwrap();
let mut s = Session::new();
s.search_rules = Some(Rules { user_content: vec![root.clone()], ..Rules::default() });
s.search_threads = Some(2);
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
s.execute("text.create", &json!({"x": 10, "y": 40, "text": "Findme", "font": "Findme Sans 3"})).unwrap();
let r = s.execute("text.missingFonts", &json!({})).unwrap();
assert_eq!((r["fonts"][0]["family"].as_str(), r["fonts"][0]["status"].as_str()), (Some("Findme Sans 3"), Some("missing")), "{r}");
s.doc_mut().unwrap().mark_saved();
let revision = s.doc().unwrap().revision;
s.execute("text.findFontFiles", &json!({"folder": root})).unwrap();
let r = finished(&mut s);
assert_eq!((&r["state"], &r["fonts"][0]["files"]), (&json!("done"), &json!([file])), "{r}");
// A file of that name is in the Fonts folder already: it stays, and the copy takes a number.
std::fs::create_dir_all(&fonts).unwrap();
std::fs::write(fonts.join("Findme.otf"), b"keep").unwrap();
let r = s.execute("text.addFontFiles", &json!({"files": [file]})).unwrap();
assert_eq!(r["copied"], json!([{"from": file, "to": fonts.join("Findme 2.otf")}]), "{r}");
assert!(r["faces"].as_u64().is_some_and(|n| n > 0) && r["families"].as_u64().is_some(), "{r}");
assert_eq!(std::fs::read(fonts.join("Findme.otf")).unwrap(), b"keep");
// The type is set in the font now, without editing the document.
let listed = s.execute("text.fonts", &json!({})).unwrap();
let findme = listed.as_array().unwrap().iter().find(|f| f["family"] == "Findme Sans 3").cloned().unwrap();
assert_eq!((findme["status"].as_str(), findme["resolved"]["family"].as_str()), (Some("exact"), Some("Findme Sans 3")), "{findme}");
let st = s.doc().unwrap();
assert!(st.revision > revision && !st.is_dirty());
assert_eq!(s.execute("text.missingFonts", &json!({})).unwrap()["count"], 0);
// A caller whose read roots hold the file but not the Fonts folder: the file is kept, and the
// reply names the copy by its file name only, without the folder.
let downloads = vectorcraft_engine::file_access::AutomationRoots::new(&[&root.join("Downloads")], &[]).unwrap();
let r = vectorcraft_engine::file_access::confine(downloads.as_ref(), || s.execute("text.addFontFiles", &json!({"files": [file]}))).unwrap();
assert_eq!((&r["folder"], &r["kept"]), (&Value::Null, &json!([{"from": file, "to": "Findme 2.otf"}])), "{r}");
assert!(!r.to_string().contains("App Fonts"), "{r}");
// The same file again is kept, not copied a third time.
let r = s.execute("text.addFontFiles", &json!({"files": [file]})).unwrap();
assert_eq!((r["copied"].clone(), r["kept"].clone()), (json!([]), json!([{"from": file, "to": fonts.join("Findme 2.otf")}])), "{r}");
assert!(!fonts.join("Findme 3.otf").exists());
// Only files the last search found are copied.
let r = s.execute("text.addFontFiles", &json!({"files": [root.join("other.ttf")]}));
assert!(r.as_ref().is_err_and(|e| e.to_string().contains("the last text.findFontFiles search")), "{r:?}");
let _ = std::fs::remove_dir_all(&root);
}