Files
vectorcraft/crates/engine/tests/font_folder.rs
T
dotCooCoo ba2531bc3f Tests: engine tests remove the temp folders they make
vectorcraft_testkit::temp_dir returns a bare path, so every engine test
that wrote into one left the folder in the system temporary folder.

- testkit: TempDir, a test's own folder made through temp_dir (same
  private creation and ownership checks) under a tag no other TempDir in
  the process uses, removed with its contents when dropped, also when
  the test panics.
- Engine unit and integration tests hold a TempDir for the whole test
  instead of calling temp_dir: graph, fileio, compare, journal, library,
  shortcut set, spelling, store, web export, cancel, containers, build,
  color settings, command sweep, font folder, removed font, model-based
  and the font search fuzz.
- The DCS and swatch library tests' own helpers and the PDF editing
  tests' shared vc-pdfedit-<pid> folder use a TempDir per test too.
- The font and link file search, store, color settings and font folder
  helpers no longer delete the private folder temp_dir checked and make
  it again with default permissions.
2026-11-13 09:33:36 -08:00

88 lines
5.1 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 dir = vectorcraft_testkit::TempDir::new("font-folder");
std::fs::create_dir_all(dir.join("Downloads/Deep")).unwrap();
let root = plain(std::fs::canonicalize(&*dir).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:?}");
}