Files
lightcraft/crates/engine/src/tests_print.rs
T
dotCooCoo a9d2c594b0 LRC-PRINT-TEMPLATES: print template tests clean up their folder on failure
The template file tests now create their folder through a Scratch guard
that removes it when the test ends, including when an assertion fails,
as tests_folders.rs and tests_slideshow_export.rs do.
2026-10-24 08:56:00 -07:00

503 lines
25 KiB
Rust

//! The `print.*` commands: state, cells, templates and the PDF.
use lightcraft_catalog::MemStore;
use serde_json::{Value, json};
use crate::Session;
use crate::library::LibraryStores;
fn run(s: &mut Session, id: &str, p: Value) -> Value {
s.execute(id, &p).unwrap_or_else(|e| panic!("{id} {p}: {e}"))
}
fn fails(s: &mut Session, id: &str, p: Value) -> String {
s.execute(id, &p).expect_err(&format!("{id} {p} should fail")).to_string()
}
/// A scratch folder that goes away with the test, however it ends.
struct Scratch(std::path::PathBuf);
impl Scratch {
fn new(tag: &str) -> Scratch {
let dir = std::env::temp_dir().join(format!("lightcraft-print-{tag}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
Scratch(dir)
}
}
impl Drop for Scratch {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn package(s: &mut Session) {
run(s, "print.set", json!({"layout": {"style": "customPackage", "page": {"paper": "letter"}}}));
}
#[test]
fn get_set_and_reset() {
let mut s = Session::with_demo();
let g = run(&mut s, "print.get", json!({}));
assert_eq!(g["settings"]["layout"]["style"], "singleImage");
assert_eq!(g["pageWidth"], 8.5);
assert_eq!(g["printable"]["x"], 0.25);
assert_eq!(g["settings"]["job"]["ppi"], 240.0);
assert!(g["papers"].as_array().unwrap().iter().any(|p| p["name"] == "a4"));
assert_eq!(g["gridCells"].as_array().unwrap().len(), 1);
let g = run(
&mut s,
"print.set",
json!({"layout": {"style": "contactSheet", "grid": {"columns": 2, "rows": 3}, "page": {"paper": "a4", "orientation": "landscape", "margins": {"left": 1.0}}}, "image": {"zoomToFill": true}, "job": {"ppi": 300.0}}),
);
assert_eq!(g["settings"]["layout"]["page"]["margins"]["left"], 1.0);
assert_eq!(g["settings"]["layout"]["page"]["margins"]["top"], 0.25, "a patch merges");
assert_eq!(g["gridCells"].as_array().unwrap().len(), 6);
assert!((g["pageWidth"].as_f64().unwrap() - 11.693).abs() < 1e-3);
assert_eq!(g["settings"]["image"]["zoomToFill"], true);
// the page count follows the photos: 6 cells per page
let g = run(&mut s, "print.get", json!({"ids": vec![1; 7]}));
assert_eq!(g["pageCount"], 2);
let g = run(&mut s, "print.reset", json!({}));
assert_eq!(g["settings"]["job"]["ppi"], 240.0);
}
#[test]
fn bad_settings_are_refused_and_change_nothing() {
let mut s = Session::with_demo();
let before = run(&mut s, "print.get", json!({}));
for p in [
json!({"typo": 1}),
json!({"layout": {"typo": 1}}),
json!({"layout": {"style": "poster"}}),
json!({"layout": {"cells": []}}),
json!({"layout": {"pages": 3}}),
json!({"layout": {"page": {"paper": "poster"}}}),
json!({"layout": {"page": {"paper": "custom", "customWidth": -1}}}),
json!({"layout": {"page": {"margins": {"top": 1e9}}}}),
json!({"layout": {"page": {"margins": {"top": 6.0, "bottom": 5.0}}}}),
json!({"layout": {"grid": {"columns": 0}}}),
json!({"layout": {"grid": {"columns": 9999}}}),
json!({"layout": {"spacing": -1}}),
json!({"image": {"borderWidth": 99}}),
json!({"image": {"strokeWidthPt": -2}}),
json!({"image": {"borderColor": [1, 2]}}),
json!({"image": {"zoomToFill": "yes"}}),
json!({"guides": {"gridSize": 0}}),
json!({"guides": {"units": "furlong"}}),
json!({"job": {"ppi": 1e30}}),
json!({"job": {"ppi": 10}}),
json!({"job": null}),
] {
fails(&mut s, "print.set", p);
}
assert_eq!(run(&mut s, "print.get", json!({})), before);
assert!(s.execute("print.set", &json!([1])).is_err());
}
#[test]
fn cells_are_commands() {
let mut s = Session::with_demo();
// grid styles have no cells
fails(&mut s, "print.cell.add", json!({"width": 2, "height": 2}));
package(&mut s);
let a = run(&mut s, "print.cell.add", json!({"width": 3.0, "height": 2.0}))["id"].as_u64().unwrap();
let b = run(&mut s, "print.cell.add", json!({"width": 3.0, "height": 2.0, "x": 5.0, "y": 5.0}))["id"].as_u64().unwrap();
let g = run(&mut s, "print.get", json!({}));
assert_eq!(g["settings"]["layout"]["cells"].as_array().unwrap().len(), 2);
assert_eq!(g["overlaps"], false);
// move, clamped inside the margins
let r = run(&mut s, "print.cell.move", json!({"id": a, "x": 99.0, "y": 0.0}));
assert_eq!(r["rect"]["x"], 8.25 - 3.0);
assert_eq!(r["rect"]["y"], 0.25);
// resize keeps what is not given
let r = run(&mut s, "print.cell.resize", json!({"id": b, "width": 4.0}));
assert_eq!((r["rect"]["w"].as_f64().unwrap(), r["rect"]["h"].as_f64().unwrap()), (4.0, 2.0));
// snapping to the other cell's edge
let ra = run(&mut s, "print.get", json!({}))["settings"]["layout"]["cells"][0]["rect"].clone();
let left = ra["x"].as_f64().unwrap();
let r = run(&mut s, "print.cell.move", json!({"id": b, "x": left - 4.0 - 0.04, "y": 5.0, "snap": true}));
assert!((r["rect"]["x"].as_f64().unwrap() - (left - 4.0)).abs() < 1e-9, "{r}");
let r = run(&mut s, "print.cell.move", json!({"id": b, "x": left - 4.0 - 0.04, "y": 5.0}));
assert!((r["rect"]["x"].as_f64().unwrap() - (left - 4.04)).abs() < 1e-9, "no snap unless asked: {r}");
run(&mut s, "print.set", json!({"guides": {"snap": "off"}}));
let r = run(&mut s, "print.cell.move", json!({"id": b, "x": left - 4.0 - 0.04, "y": 5.0, "snap": true}));
assert!((r["rect"]["x"].as_f64().unwrap() - (left - 4.04)).abs() < 1e-9, "snap off");
// stacking
let r = run(&mut s, "print.cell.stack", json!({"id": a, "order": "toFront"}));
assert_eq!(r["order"], json!([b, a]));
fails(&mut s, "print.cell.stack", json!({"id": a, "order": "sideways"}));
// photos and pages
let photo = s.catalog.photos().next().unwrap().id.0;
run(&mut s, "print.cell.photo", json!({"id": a, "photo": photo}));
fails(&mut s, "print.cell.photo", json!({"id": a, "photo": 999_999}));
run(&mut s, "print.layout.newPage", json!({}));
run(&mut s, "print.cell.page", json!({"id": b, "page": 1}));
fails(&mut s, "print.cell.page", json!({"id": b, "page": 7}));
let g = run(&mut s, "print.get", json!({}));
assert_eq!(g["pageCount"], 2);
// delete, clear
run(&mut s, "print.cell.delete", json!({"id": b}));
fails(&mut s, "print.cell.delete", json!({"id": b}));
fails(&mut s, "print.cell.move", json!({"id": 99, "x": 0, "y": 0}));
fails(&mut s, "print.cell.move", json!({"x": 0, "y": 0}));
run(&mut s, "print.layout.clear", json!({}));
let g = run(&mut s, "print.get", json!({}));
assert_eq!((g["settings"]["layout"]["cells"].as_array().unwrap().len(), g["pageCount"].as_u64()), (0, Some(1)));
}
#[test]
fn hostile_cell_numbers_are_refused() {
let mut s = Session::with_demo();
package(&mut s);
for (w, h) in [(0.0, 2.0), (-1.0, 2.0), (1e12, 1e12), (2.0, 1e-9), (50.0, 50.0)] {
fails(&mut s, "print.cell.add", json!({"width": w, "height": h}));
}
fails(&mut s, "print.cell.add", json!({"width": "wide", "height": 2}));
fails(&mut s, "print.cell.add", json!({"width": 2}));
let id = run(&mut s, "print.cell.add", json!({"width": 2, "height": 2}))["id"].as_u64().unwrap();
fails(&mut s, "print.cell.move", json!({"id": id, "x": "left", "y": 0}));
fails(&mut s, "print.cell.resize", json!({"id": id, "width": -4}));
fails(&mut s, "print.cell.resize", json!({"id": id, "width": "x"}));
fails(&mut s, "print.cell.move", json!({"id": id, "x": 1, "y": 1, "snap": "yes"}));
fails(&mut s, "print.cell.move", json!({"id": u64::MAX, "x": 1, "y": 1}));
let r = run(&mut s, "print.cell.move", json!({"id": id, "x": -1e300, "y": 1e300}));
assert_eq!(r["rect"]["x"], 0.25);
}
#[test]
fn auto_layout_fills_pages_without_overlap() {
let mut s = Session::with_demo();
run(&mut s, "print.set", json!({"layout": {"style": "picturePackage"}}));
for (w, h) in [(4.0, 6.0), (4.0, 6.0), (5.0, 7.0), (2.5, 3.5), (2.5, 3.5), (2.5, 3.5), (2.5, 3.5), (8.0, 10.0)] {
run(&mut s, "print.cell.add", json!({"width": w, "height": h}));
}
let r = run(&mut s, "print.layout.autoLayout", json!({}));
assert!(r["pages"].as_u64().unwrap() >= 2);
let g = run(&mut s, "print.get", json!({}));
assert_eq!(g["overlaps"], false);
let again = run(&mut s, "print.layout.autoLayout", json!({}));
assert_eq!(again["cells"], r["cells"], "deterministic");
// an oversize cell that arrived by a paper change is pulled in, not left outside the page
run(&mut s, "print.set", json!({"layout": {"page": {"paper": "4x6"}}}));
let g = run(&mut s, "print.get", json!({}));
for c in g["settings"]["layout"]["cells"].as_array().unwrap() {
assert!(c["rect"]["x"].as_f64().unwrap() + c["rect"]["w"].as_f64().unwrap() <= 4.0 - 0.25 + 1e-9);
}
}
/// The first page's MediaBox width and height in points.
fn media_box(pdf: &[u8]) -> (f64, f64) {
let text = String::from_utf8_lossy(pdf);
let at = text.find("/MediaBox [").expect("a MediaBox") + "/MediaBox [".len();
let nums: Vec<f64> = text[at..].split(']').next().unwrap().split_whitespace().map(|v| v.parse().unwrap()).collect();
(nums[2], nums[3])
}
fn temp_pdf(name: &str) -> String {
std::env::temp_dir().join(format!("lightcraft-print-{name}-{}.pdf", std::process::id())).to_string_lossy().to_string()
}
#[test]
fn export_pdf_prints_the_current_settings() {
let mut s = Session::with_demo();
let ids: Vec<u64> = s.catalog.photos().take(3).map(|p| p.id.0).collect();
run(
&mut s,
"print.set",
json!({"layout": {"style": "contactSheet", "grid": {"columns": 1, "rows": 2}, "page": {"paper": "a5"}}, "job": {"ppi": 100.0}}),
);
let path = temp_pdf("export");
let r = run(&mut s, "print.exportPdf", json!({"path": path, "ids": ids}));
assert_eq!((r["pages"].as_u64(), r["photos"].as_u64()), (Some(2), Some(3)));
let bytes = std::fs::read(&path).unwrap();
assert_eq!(bytes.len() as u64, r["bytes"].as_u64().unwrap());
let text = String::from_utf8_lossy(&bytes);
assert!(text.starts_with("%PDF-") && text.contains("/Count 2"));
// A5 is 148 x 210 mm = 419.53 x 595.28 pt, rounded to whole pixels at 100 ppi (583 x 827)
let (w, h) = media_box(&bytes);
assert!((w - 419.53).abs() < 1.0 && (h - 595.28).abs() < 1.0, "A5, got {w} x {h} pt");
// the row in the activity stack is gone when the print is done
assert!(s.activity.list().is_empty());
fails(&mut s, "print.exportPdf", json!({}));
fails(&mut s, "print.exportPdf", json!({"path": path, "typo": 1}));
fails(&mut s, "print.exportPdf", json!({"path": path, "ids": ["x"]}));
fails(&mut s, "print.exportPdf", json!({"path": path, "ids": [999_999]}));
// a package with no cells has nothing to print
run(&mut s, "print.set", json!({"layout": {"style": "picturePackage"}}));
fails(&mut s, "print.exportPdf", json!({"path": path, "ids": ids}));
}
#[test]
fn export_pdf_with_a_printer_profile() {
let mut s = Session::with_demo();
let ids: Vec<u64> = s.catalog.photos().take(1).map(|p| p.id.0).collect();
let dir = std::env::temp_dir().join(format!("lightcraft-print-icc-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let icc = dir.join("Fine Art Matte.icc");
std::fs::write(&icc, lightcraft_codecs::icc::write_named(lightcraft_codecs::NamedSpace::AdobeRgb)).unwrap();
let color = json!({"profile": {"kind": "icc", "path": icc}, "intent": "relative", "sharpen": "high", "media": "matte", "brightness": 10});
run(
&mut s,
"print.set",
json!({"layout": {"style": "contactSheet", "grid": {"columns": 1, "rows": 1}, "page": {"paper": "4x6"}}, "job": {"ppi": 72.0, "color": color}}),
);
assert_eq!(run(&mut s, "print.get", json!({}))["settings"]["job"]["color"]["media"], "matte");
let path = temp_pdf("icc");
run(&mut s, "print.exportPdf", json!({"path": path, "ids": ids}));
let text = String::from_utf8_lossy(&std::fs::read(&path).unwrap()).into_owned();
assert!(text.contains("/OutputIntents") && text.contains("/ICCBased") && text.contains("/BitsPerComponent 16"));
// out-of-range adjustments are refused when set; a missing profile fails before rendering
fails(&mut s, "print.set", json!({"job": {"color": {"contrast": 150}}}));
run(&mut s, "print.set", json!({"job": {"color": {"profile": {"kind": "icc", "path": dir.join("gone.icc")}}}}));
let err = fails(&mut s, "print.exportPdf", json!({"path": path, "ids": ids}));
assert!(err.contains("gone.icc"), "{err}");
assert!(s.activity.list().is_empty(), "nothing started");
}
#[test]
fn custom_package_prints_each_cells_photo() {
let mut s = Session::with_demo();
let ids: Vec<u64> = s.catalog.photos().take(2).map(|p| p.id.0).collect();
run(&mut s, "print.set", json!({"layout": {"style": "customPackage", "page": {"paper": "4x6"}}, "job": {"ppi": 72.0}}));
let a = run(&mut s, "print.cell.add", json!({"width": 3.0, "height": 2.0}))["id"].as_u64().unwrap();
let b = run(&mut s, "print.cell.add", json!({"width": 3.0, "height": 2.0}))["id"].as_u64().unwrap();
run(&mut s, "print.cell.photo", json!({"id": a, "photo": ids[0]}));
run(&mut s, "print.cell.photo", json!({"id": b, "photo": ids[1]}));
let r = run(&mut s, "print.exportPdf", json!({"path": temp_pdf("custom"), "ids": [ids[0]]}));
assert_eq!(r["pages"], 1);
}
#[test]
fn templates_save_apply_delete_and_persist() {
let files = MemStore::new();
let open = |files: &MemStore| {
let mut s = Session::with_demo();
let stores = LibraryStores { dir: "print".into(), catalog: Box::new(MemStore::new()), files: Box::new(files.clone()), on_disk: false };
s.open_library_in(stores, false).unwrap();
s
};
let mut s = open(&files);
run(
&mut s,
"print.set",
json!({"layout": {"style": "contactSheet", "grid": {"columns": 4, "rows": 5}}, "page": {"background": [10, 20, 30]}, "guides": {"gridSize": 1.0}}),
);
let r = run(&mut s, "print.template.save", json!({"name": "Proofs"}));
assert_eq!(r["templates"][0]["name"], "Proofs");
assert!(r["templates"][0]["settings"].get("guides").is_none());
run(&mut s, "print.template.save", json!({"name": "proofs"}));
assert_eq!(run(&mut s, "print.template.list", json!({}))["templates"].as_array().unwrap().len(), 1, "the same name in another case replaces");
run(&mut s, "print.reset", json!({}));
run(&mut s, "print.set", json!({"guides": {"gridSize": 0.5}}));
let g = run(&mut s, "print.template.apply", json!({"name": "PROOFS"}));
assert_eq!(g["settings"]["layout"]["grid"]["columns"], 4);
assert_eq!(g["settings"]["page"]["background"], json!([10, 20, 30]));
assert_eq!(g["settings"]["guides"]["gridSize"], 0.5, "guides stay as they were");
// it survives reopening the library
let mut again = open(&files);
assert_eq!(run(&mut again, "print.template.list", json!({}))["templates"][0]["name"], "proofs");
run(&mut again, "print.template.apply", json!({"name": "Proofs"}));
fails(&mut s, "print.template.save", json!({"name": " "}));
fails(&mut s, "print.template.save", json!({}));
fails(&mut s, "print.template.apply", json!({"name": "Nope"}));
fails(&mut s, "print.template.delete", json!({"name": "Nope"}));
run(&mut s, "print.template.delete", json!({"name": "proofs"}));
assert!(run(&mut s, "print.template.list", json!({}))["templates"].as_array().unwrap().is_empty());
assert!(run(&mut open(&files), "print.template.list", json!({}))["templates"].as_array().unwrap().is_empty());
}
#[test]
fn templates_export_to_a_file_and_import_from_one() {
let files = MemStore::new();
let open = |files: &MemStore| {
let mut s = Session::with_demo();
let stores = LibraryStores { dir: "print".into(), catalog: Box::new(MemStore::new()), files: Box::new(files.clone()), on_disk: false };
s.open_library_in(stores, false).unwrap();
s
};
let scratch = Scratch::new("templates");
let dir = &scratch.0;
let mut s = open(&files);
run(&mut s, "print.set", json!({"layout": {"style": "contactSheet", "grid": {"columns": 3, "rows": 4}}, "page": {"background": [5, 6, 7]}}));
run(&mut s, "print.template.save", json!({"name": "Proofs"}));
// the extension is added when the path has none
let r = run(&mut s, "print.template.export", json!({"name": "proofs", "path": dir.join("proofs").to_string_lossy()}));
assert_eq!(r["name"], "Proofs");
let file = dir.join("proofs.lcprint");
assert_eq!(r["path"], file.to_string_lossy().as_ref());
let doc: Value = serde_json::from_slice(&std::fs::read(&file).unwrap()).unwrap();
assert_eq!((doc["format"].as_str(), doc["version"].as_u64(), doc["name"].as_str()), (Some("lightcraft.printTemplate"), Some(1), Some("Proofs")));
// into the same library: the name is taken, so the copy is renamed
let r = run(&mut s, "print.template.import", json!({"path": file.to_string_lossy()}));
assert_eq!((r["name"].as_str(), r["renamed"].as_bool()), (Some("Proofs (2)"), Some(true)), "{r}");
assert_eq!(r["templates"].as_array().unwrap().len(), 2);
assert_eq!(r["templates"][1]["settings"], r["templates"][0]["settings"]);
// into another library, under its own name, and it is kept there
let other = MemStore::new();
let mut b = open(&other);
let r = run(&mut b, "print.template.import", json!({"path": file.to_string_lossy()}));
assert_eq!((r["name"].as_str(), r["renamed"].as_bool()), (Some("Proofs"), Some(false)));
let g = run(&mut open(&other), "print.template.apply", json!({"name": "Proofs"}));
assert_eq!(g["settings"]["layout"]["grid"]["columns"], 3);
assert_eq!(g["settings"]["page"]["background"], json!([5, 6, 7]));
// refusals leave the templates as they were
let bad_file = dir.join("bad.lcprint");
std::fs::write(&bad_file, br#"{"format": "lightcraft.printTemplate", "version": 1, "name": "Bad", "settings": {"layout": {"pages": 0}}}"#)
.unwrap();
let huge = dir.join("huge.lcprint");
std::fs::write(&huge, vec![b' '; crate::print::template::MAX_FILE as usize + 1]).unwrap();
for (p, why) in [
(json!({}), "missing `path`"),
(json!({"path": " "}), "missing `path`"),
(json!({"path": dir.join("absent.lcprint").to_string_lossy()}), "absent.lcprint"),
(json!({"path": dir.to_string_lossy()}), "not a file"),
(json!({"path": huge.to_string_lossy()}), "too large"),
(json!({"path": bad_file.to_string_lossy()}), "template `Bad`"),
] {
let e = fails(&mut s, "print.template.import", p.clone());
assert!(e.contains(why), "{p}: {e}");
}
assert_eq!(run(&mut s, "print.template.list", json!({}))["templates"].as_array().unwrap().len(), 2);
assert!(fails(&mut s, "print.template.export", json!({"name": "Nope", "path": dir.join("x").to_string_lossy()})).contains("no print template"));
assert!(fails(&mut s, "print.template.export", json!({"name": "Proofs"})).contains("missing `path`"));
assert!(fails(&mut s, "print.template.export", json!({"path": dir.join("x").to_string_lossy()})).contains("missing `name`"));
assert!(!dir.join("x.lcprint").exists());
// a full list takes no more
while s.print_templates.len() < crate::print::template::MAX_TEMPLATES {
let n = s.print_templates.len();
run(&mut s, "print.template.save", json!({"name": format!("T{n}")}));
}
assert!(fails(&mut s, "print.template.import", json!({"path": file.to_string_lossy()})).contains("at most"));
}
/// A library whose `prefs.json` can't be read, so saving the preferences fails.
#[derive(Clone, Default)]
struct PrefsUnreadable(MemStore);
impl lightcraft_catalog::Store for PrefsUnreadable {
fn read(&mut self, name: &str) -> std::io::Result<Option<Vec<u8>>> {
if name == "prefs.json" {
return Err(std::io::Error::other("locked by another process"));
}
self.0.read(name)
}
fn write_atomic(&mut self, name: &str, data: &[u8]) -> std::io::Result<()> {
self.0.write_atomic(name, data)
}
fn append(&mut self, name: &str, data: &[u8]) -> std::io::Result<()> {
self.0.append(name, data)
}
fn truncate(&mut self, name: &str, len: u64) -> std::io::Result<()> {
self.0.truncate(name, len)
}
fn describe(&self) -> String {
"prefs unreadable".into()
}
}
#[test]
fn template_changes_that_cant_be_saved_are_undone() {
let mut s = Session::with_demo();
let stores =
LibraryStores { dir: "print".into(), catalog: Box::new(MemStore::new()), files: Box::new(PrefsUnreadable::default()), on_disk: false };
s.open_library_in(stores, false).unwrap();
s.print_templates.push(crate::print::template::PrintTemplate::new("Proofs", &s.print).unwrap());
let names = |s: &Session| s.print_templates.iter().map(|t| (t.name.clone(), t.settings.clone())).collect::<Vec<_>>();
let kept = names(&s);
let scratch = Scratch::new("unsaved");
let file = scratch.0.join("proofs.lcprint");
run(&mut s, "print.template.export", json!({"name": "Proofs", "path": file.to_string_lossy()}));
run(&mut s, "print.set", json!({"layout": {"style": "contactSheet"}}));
for (id, p) in [
("print.template.save", json!({"name": "Other"})),
("print.template.save", json!({"name": "proofs"})),
("print.template.delete", json!({"name": "Proofs"})),
("print.template.import", json!({"path": file.to_string_lossy()})),
] {
assert!(fails(&mut s, id, p.clone()).contains("prefs.json"), "{id} {p}");
assert_eq!(names(&s), kept, "{id} {p}");
}
}
#[test]
fn a_hostile_template_in_the_preferences_does_not_break_the_library() {
let files = MemStore::new();
let hostile = json!({"printTemplates": [
{"name": "Bad", "settings": {"layout": {"pages": 0}, "extra": true}},
{"name": "Huge", "settings": {"job": {"ppi": 1e300}}},
{"name": "Fine", "settings": {"layout": {"style": "contactSheet"}}}
], "cacheMb": 123});
files.set("prefs.json", serde_json::to_vec(&hostile).unwrap());
let mut s = Session::with_demo();
let stores = LibraryStores { dir: "print".into(), catalog: Box::new(MemStore::new()), files: Box::new(files.clone()), on_disk: false };
s.open_library_in(stores, false).unwrap();
assert!(s.take_library_warnings().is_empty(), "the preferences are not damaged");
assert_eq!(s.cache_mb, 123);
assert_eq!(run(&mut s, "print.template.list", json!({}))["templates"].as_array().unwrap().len(), 3);
fails(&mut s, "print.template.apply", json!({"name": "Bad"}));
fails(&mut s, "print.template.apply", json!({"name": "Huge"}));
run(&mut s, "print.template.apply", json!({"name": "Fine"}));
}
#[test]
fn every_print_command_is_registered_with_a_description() {
for id in [
"print.get",
"print.set",
"print.reset",
"print.cell.add",
"print.cell.move",
"print.cell.resize",
"print.cell.delete",
"print.cell.stack",
"print.cell.photo",
"print.cell.page",
"print.layout.autoLayout",
"print.layout.newPage",
"print.layout.clear",
"print.exportPdf",
"print.template.list",
"print.template.save",
"print.template.apply",
"print.template.delete",
"print.template.export",
"print.template.import",
] {
let c = crate::find_command(id).unwrap_or_else(|| panic!("{id} missing"));
assert!(c.params.len() > 3, "{id}");
}
}
#[test]
fn page_options_are_print_settings_and_template_fields() {
let mut s = Session::with_demo();
let g = run(&mut s, "print.get", json!({}));
assert_eq!(g["settings"]["page"], json!({"background": [255, 255, 255], "pageNumbers": false, "pageInfo": false, "cropMarks": false}));
let g = run(&mut s, "print.set", json!({"page": {"pageNumbers": true, "cropMarks": true}}));
assert_eq!((g["settings"]["page"]["pageNumbers"].as_bool(), g["settings"]["page"]["pageInfo"].as_bool()), (Some(true), Some(false)));
assert_eq!(g["settings"]["page"]["background"], json!([255, 255, 255]), "a patch merges");
fails(&mut s, "print.set", json!({"page": {"pageInfo": "yes"}}));
fails(&mut s, "print.set", json!({"page": {"pageNumber": true}}));
// a template keeps them
run(&mut s, "print.template.save", json!({"name": "Marked"}));
run(&mut s, "print.reset", json!({}));
assert!(!s.print.page.crop_marks);
run(&mut s, "print.template.apply", json!({"name": "Marked"}));
assert!(s.print.page.page_numbers && s.print.page.crop_marks && !s.print.page.page_info);
let set = crate::find_command("print.set").unwrap();
assert!(set.params.contains("pageNumbers") && set.params.contains("pageInfo") && set.params.contains("cropMarks"), "{}", set.params);
}