482 lines
23 KiB
Rust
482 lines
23 KiB
Rust
//! Placed documents: a VectorCraft document placed linked (`file.place`'s default for one read
|
||
//! from a file), drawn and output as vectors, saved with its preview, checked, updated and
|
||
//! relinked when the file changes, and turned into an editable copy (Break Link, Expand).
|
||
|
||
use serde_json::{Value, json};
|
||
use vectorcraft_color::{Color, Paint};
|
||
use vectorcraft_doc::{Appearance, Document, Node, NodeKind, PlacedDocument};
|
||
use vectorcraft_geom::{Rect, shapes};
|
||
|
||
use super::tests_links::{BLUE, Folder, RED, centre_colour, near, open, save, session, write};
|
||
use super::*;
|
||
|
||
/// A VectorCraft document at `path`: one `w`×`h` artboard filled with `rgb` (and `extra` more
|
||
/// rectangles, which change the file's size).
|
||
fn source(path: &str, w: f64, h: f64, rgb: [u8; 3], extra: usize) {
|
||
let mut d = Document::new(w, h);
|
||
let layer = d.layers[0].id;
|
||
let [r, g, b] = rgb;
|
||
for i in 0..=extra {
|
||
let id = d.alloc_id();
|
||
let look = Appearance::basic(Paint::solid(Color::rgb8(r, g, b)), Paint::None, 0.0);
|
||
d.insert(Some(layer), i, Node::path(id, shapes::rectangle(Rect::new(0.0, 0.0, w, h)), look)).unwrap();
|
||
}
|
||
write(path, &vectorcraft_format::save_file(&d));
|
||
}
|
||
|
||
/// A document drawing symbol `Star` (a blue 100×50 rectangle) at `path`, titled `title` (tests
|
||
/// that remove the file give it content of its own: files read are cached by content).
|
||
fn star_source(path: &str, title: &str) {
|
||
let mut d = Document::new(100.0, 50.0);
|
||
d.title = title.into();
|
||
let layer = d.layers[0].id;
|
||
let art = Node::path(vectorcraft_doc::NodeId(900), shapes::rectangle(Rect::new(0.0, 0.0, 100.0, 50.0)), look(BLUE));
|
||
d.symbols.push(vectorcraft_doc::Symbol { name: "Star".into(), art: std::sync::Arc::new(art) });
|
||
let id = d.alloc_id();
|
||
d.insert(Some(layer), 0, Node::new(id, NodeKind::SymbolInstance { symbol: "Star".into(), xf: vectorcraft_geom::Affine::IDENTITY })).unwrap();
|
||
write(path, &vectorcraft_format::save_file(&d));
|
||
}
|
||
|
||
fn look(c: [u8; 3]) -> Appearance {
|
||
Appearance::basic(Paint::solid(Color::rgb8(c[0], c[1], c[2])), Paint::None, 0.0)
|
||
}
|
||
|
||
/// The active document gets a red symbol named `Star` of its own.
|
||
fn parent_star(s: &mut Session) {
|
||
let star = Node::path(vectorcraft_doc::NodeId(901), shapes::rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)), look(RED));
|
||
std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc)
|
||
.symbols
|
||
.push(vectorcraft_doc::Symbol { name: "Star".into(), art: std::sync::Arc::new(star) });
|
||
}
|
||
|
||
/// Place `path` (linked: the default), centred on the artboard → its id.
|
||
fn place(s: &mut Session, path: &str) -> NodeId {
|
||
let r = s.execute("file.place", &json!({"path": path, "at": [200, 150]})).unwrap();
|
||
assert_eq!(r["linked"], true, "{r}");
|
||
NodeId(r["ids"][0].as_u64().unwrap())
|
||
}
|
||
|
||
fn placed(s: &Session, id: NodeId) -> PlacedDocument {
|
||
match &s.doc().unwrap().doc.node(id).unwrap().kind {
|
||
NodeKind::PlacedDocument(p) => (**p).clone(),
|
||
k => panic!("not a placed document: {k:?}"),
|
||
}
|
||
}
|
||
|
||
fn bounds(s: &Session, id: NodeId) -> Rect {
|
||
s.doc().unwrap().doc.node(id).unwrap().geometric_bounds().unwrap()
|
||
}
|
||
|
||
fn decode(v: &Value) -> Vec<u8> {
|
||
vectorcraft_format::base64_decode(v["dataBase64"].as_str().unwrap()).unwrap()
|
||
}
|
||
|
||
/// Images and paths in `bytes`, a PDF, read back.
|
||
fn pdf_contents(bytes: &[u8]) -> (usize, usize) {
|
||
// The page as drawn (not the VectorCraft document the PDF carries).
|
||
let (doc, _) = super::cmd::fileio::page_document(bytes, 0, &Default::default()).unwrap();
|
||
let (mut images, mut paths) = (0, 0);
|
||
doc.walk(|n| match n.kind {
|
||
NodeKind::Image(_) => images += 1,
|
||
NodeKind::Path { .. } | NodeKind::Compound { .. } => paths += 1,
|
||
_ => {}
|
||
});
|
||
(images, paths)
|
||
}
|
||
|
||
/// The JSON of a `.vectorcraft` file (the data after it left out).
|
||
fn file_json(bytes: &[u8]) -> Value {
|
||
let end = bytes.windows(vectorcraft_format::BLOB_MAGIC.len()).position(|w| w == vectorcraft_format::BLOB_MAGIC).unwrap_or(bytes.len());
|
||
serde_json::from_slice(&bytes[..end]).unwrap()
|
||
}
|
||
|
||
#[test]
|
||
fn a_document_places_linked_by_default_at_its_artboard_size() {
|
||
let dir = Folder::new("placed-basic");
|
||
let src = dir.file("logo.vectorcraft");
|
||
source(&src, 100.0, 50.0, RED, 0);
|
||
let mut s = session();
|
||
let id = place(&mut s, &src);
|
||
let p = placed(&s, id);
|
||
assert!((p.width - 100.0).abs() < 1e-6 && (p.height - 50.0).abs() < 1e-6, "{p:?}");
|
||
assert_eq!(bounds(&s, id), Rect::new(150.0, 125.0, 250.0, 175.0));
|
||
assert_eq!((p.link.path.as_str(), p.link.page), (src.as_str(), Some(1)));
|
||
let n = s.doc().unwrap().doc.node(id).unwrap();
|
||
assert_eq!((n.name.as_deref(), n.kind_label()), (Some("logo.vectorcraft"), "Placed Document"));
|
||
assert!(near(centre_colour(&s.doc().unwrap().doc), RED));
|
||
// It moves and scales like any object.
|
||
s.execute("object.move", &json!({"dx": 10, "dy": 0})).unwrap();
|
||
assert_eq!(bounds(&s, id), Rect::new(160.0, 125.0, 260.0, 175.0));
|
||
// Its art's bounds instead of the artboard.
|
||
let mut d = Document::new(100.0, 50.0);
|
||
let layer = d.layers[0].id;
|
||
let id2 = d.alloc_id();
|
||
d.insert(Some(layer), 0, Node::path(id2, shapes::rectangle(Rect::new(10.0, 10.0, 40.0, 30.0)), look(RED))).unwrap();
|
||
let small = dir.file("small.vectorcraft");
|
||
write(&small, &vectorcraft_format::save_file(&d));
|
||
let r = s.execute("file.place", &json!({"path": small, "crop": "bounding", "at": [200, 150]})).unwrap();
|
||
let p = placed(&s, NodeId(r["ids"][0].as_u64().unwrap()));
|
||
assert!(p.bounding && (p.width - 30.0).abs() < 1e-6 && (p.height - 20.0).abs() < 1e-6, "{p:?}");
|
||
}
|
||
|
||
#[test]
|
||
fn without_link_or_a_file_a_document_places_as_an_editable_copy() {
|
||
let dir = Folder::new("placed-copy");
|
||
let src = dir.file("logo.vectorcraft");
|
||
source(&src, 100.0, 50.0, RED, 0);
|
||
let mut s = session();
|
||
let data = vectorcraft_format::base64_encode(&std::fs::read(&src).unwrap());
|
||
for p in [json!({"path": src, "link": false}), json!({"name": "logo.vectorcraft", "dataBase64": data})] {
|
||
let r = s.execute("file.place", &p).unwrap();
|
||
assert_eq!(r["linked"], false, "{r}");
|
||
let n = s.doc().unwrap().doc.node(NodeId(r["ids"][0].as_u64().unwrap())).unwrap();
|
||
assert_eq!(n.kind_label(), "Clip Group", "{p}");
|
||
}
|
||
assert!(!s.doc().unwrap().doc.has_placed());
|
||
}
|
||
|
||
#[test]
|
||
fn a_placed_document_is_vectors_in_every_output() {
|
||
let dir = Folder::new("placed-outputs");
|
||
let src = dir.file("logo.vectorcraft");
|
||
source(&src, 100.0, 50.0, RED, 0);
|
||
let mut s = session();
|
||
place(&mut s, &src);
|
||
let svg = s.execute("document.serialize", &json!({"format": "svg"})).unwrap();
|
||
let text = svg["text"].as_str().unwrap();
|
||
assert!(!text.contains("<image") && (text.contains("<path") || text.contains("<rect")), "drawn as paths: {text}");
|
||
let (images, paths) = pdf_contents(&decode(&s.execute("document.serialize", &json!({"format": "pdf"})).unwrap()));
|
||
assert_eq!(images, 0, "no image in the PDF");
|
||
assert!(paths > 0);
|
||
let png = decode(&s.execute("document.serialize", &json!({"format": "png"})).unwrap());
|
||
let [r, g, b, _] = image::load_from_memory(&png).unwrap().to_rgba8().get_pixel(200, 150).0;
|
||
assert!(near([r, g, b], RED), "{:?}", [r, g, b]);
|
||
for format in ["eps", "emf", "dxf"] {
|
||
let out = s.execute("document.serialize", &json!({"format": format})).unwrap();
|
||
assert!(out.to_string().len() > 200, "{format}: {out}");
|
||
}
|
||
let info = s.execute("document.info", &json!({})).unwrap();
|
||
assert!(info.to_string().contains("\"placedDocuments\""), "{info}");
|
||
}
|
||
|
||
#[test]
|
||
fn the_preview_is_saved_so_the_document_shows_it_without_its_file() {
|
||
let dir = Folder::new("placed-saved");
|
||
let src = dir.file("logo.vectorcraft");
|
||
// Content of its own: files read are cached by content, and this one is removed.
|
||
source(&src, 100.0, 50.0, RED, 2);
|
||
let mut s = session();
|
||
let id = place(&mut s, &src);
|
||
let doc_path = dir.file("poster/poster.vectorcraft");
|
||
std::fs::create_dir_all(dir.file("poster")).unwrap();
|
||
save(&mut s, &doc_path);
|
||
let saved: Value = serde_json::from_slice(&std::fs::read(&doc_path).unwrap()).unwrap();
|
||
let kind = &saved["document"]["layers"][0]["kind"]["children"][0]["kind"];
|
||
assert_eq!(kind["type"], "placeddocument");
|
||
assert_eq!(kind["link"]["relative"], "../logo.vectorcraft");
|
||
let key = kind["key"].as_str().unwrap();
|
||
let entry = &saved["images"][key];
|
||
assert_eq!((entry["proxy"].as_bool(), entry["mime"].as_str()), (Some(true), Some("image/jpeg")), "the preview, as JPEG (opaque)");
|
||
// Include Linked Files keeps the file itself.
|
||
let full = file_json(&decode(&s.execute("document.serialize", &json!({"format": "vectorcraft", "includeLinked": true})).unwrap()));
|
||
assert_eq!((full["images"][key]["proxy"].clone(), full["images"][key]["mime"].as_str()), (Value::Null, Some("application/json")));
|
||
std::fs::remove_file(&src).unwrap();
|
||
let r = open(&mut s, &doc_path);
|
||
assert_eq!(r["missingLinks"].as_array().map(Vec::len), Some(1), "{r}");
|
||
assert_eq!(placed(&s, id).link.path, src);
|
||
assert!(near(centre_colour(&s.doc().unwrap().doc), RED), "shown from the saved preview");
|
||
assert_eq!(s.execute("links.check", &json!({})).unwrap()["missing"], 1);
|
||
// Output with the file gone: the preview, with a warning.
|
||
let png = s.execute("document.serialize", &json!({"format": "png"})).unwrap();
|
||
assert!(png["warnings"].to_string().contains("output as their previews"), "{png}");
|
||
let [r, g, b, _] = image::load_from_memory(&decode(&png)).unwrap().to_rgba8().get_pixel(200, 150).0;
|
||
assert!(near([r, g, b], RED), "{:?}", [r, g, b]);
|
||
// Saved for older apps: the art it shows (here its preview), as plain objects.
|
||
let old: Value =
|
||
serde_json::from_slice(&decode(&s.execute("document.serialize", &json!({"format": "vectorcraft", "version": 2})).unwrap())).unwrap();
|
||
let old = old["document"]["layers"][0]["kind"]["children"][0].to_string();
|
||
assert!(old.contains("\"image\"") && !old.contains("placeddocument"), "{old}");
|
||
}
|
||
|
||
#[test]
|
||
fn a_document_saved_with_previews_outputs_its_files_art() {
|
||
let dir = Folder::new("placed-preview-output");
|
||
let src = dir.file("logo.vectorcraft");
|
||
source(&src, 100.0, 50.0, RED, 0);
|
||
let mut s = session();
|
||
let id = place(&mut s, &src);
|
||
let doc_path = dir.file("poster.vectorcraft");
|
||
save(&mut s, &doc_path);
|
||
open(&mut s, &doc_path);
|
||
let key = placed(&s, id).key;
|
||
assert!(s.doc().unwrap().doc.images[&key].is_proxy(), "opened with the preview only");
|
||
assert!(near(centre_colour(&s.doc().unwrap().doc), RED));
|
||
// Output reads the file again: vectors, no warning.
|
||
let svg = s.execute("document.serialize", &json!({"format": "svg"})).unwrap();
|
||
assert_eq!(svg["warnings"], json!([]), "{}", svg["warnings"]);
|
||
let text = svg["text"].as_str().unwrap();
|
||
assert!(!text.contains("<image") && (text.contains("<path") || text.contains("<rect")), "vectors");
|
||
// So does saving with Include Linked Files, and the document stays as it was.
|
||
let full = file_json(&decode(&s.execute("document.serialize", &json!({"format": "vectorcraft", "includeLinked": true})).unwrap()));
|
||
assert_eq!(full["images"][&key]["mime"], "application/json");
|
||
assert!(s.doc().unwrap().doc.images[&key].is_proxy());
|
||
}
|
||
|
||
#[test]
|
||
fn a_changed_file_is_modified_and_update_links_reads_it_again() {
|
||
let dir = Folder::new("placed-update");
|
||
let src = dir.file("logo.vectorcraft");
|
||
source(&src, 100.0, 50.0, RED, 0);
|
||
let mut s = session();
|
||
let id = place(&mut s, &src);
|
||
s.execute("object.scale", &json!({"sx": 200})).unwrap();
|
||
let before = bounds(&s, id);
|
||
let c = s.execute("links.check", &json!({})).unwrap();
|
||
assert_eq!((c["modified"].as_u64(), c["links"][0]["status"].as_str()), (Some(0), Some("ok")), "{c}");
|
||
let list = s.execute("links.list", &json!({})).unwrap();
|
||
let row = &list["links"][0];
|
||
assert_eq!((row["format"].as_str(), row["document"].as_bool(), row["status"].as_str()), (Some("VectorCraft"), Some(true), Some("ok")), "{row}");
|
||
// A blue document, wider, and a different file size.
|
||
source(&src, 200.0, 50.0, BLUE, 1);
|
||
assert_eq!(s.execute("links.check", &json!({})).unwrap()["links"][0]["status"], "modified");
|
||
// Until updated, it shows (and outputs) the file as it read it.
|
||
let svg = s.execute("document.serialize", &json!({"format": "svg"})).unwrap();
|
||
assert_eq!(svg["warnings"], json!([]));
|
||
assert!(near(centre_colour(&s.doc().unwrap().doc), RED));
|
||
let u = s.execute("links.update", &json!({})).unwrap();
|
||
assert_eq!(u["updated"], json!([id.0]), "{u}");
|
||
assert_eq!(placed(&s, id).width, 200.0);
|
||
assert_eq!(bounds(&s, id), before, "keeps its bounds");
|
||
assert!(near(centre_colour(&s.doc().unwrap().doc), BLUE));
|
||
assert_eq!(s.execute("links.check", &json!({})).unwrap()["modified"], 0);
|
||
// One undo step.
|
||
s.execute("edit.undo", &json!({})).unwrap();
|
||
assert!(near(centre_colour(&s.doc().unwrap().doc), RED));
|
||
}
|
||
|
||
#[test]
|
||
fn update_links_on_open_reads_a_modified_file_again() {
|
||
let dir = Folder::new("placed-open");
|
||
let src = dir.file("logo.vectorcraft");
|
||
source(&src, 100.0, 50.0, RED, 0);
|
||
let mut s = session();
|
||
place(&mut s, &src);
|
||
let doc_path = dir.file("poster.vectorcraft");
|
||
save(&mut s, &doc_path);
|
||
source(&src, 100.0, 50.0, BLUE, 1);
|
||
// Ask When Modified (the default): reported, left as it was.
|
||
let r = open(&mut s, &doc_path);
|
||
assert_eq!(r["modifiedLinks"].as_array().map(Vec::len), Some(1), "{r}");
|
||
assert!(near(centre_colour(&s.doc().unwrap().doc), RED));
|
||
// Automatically: read again as the document opens.
|
||
s.execute("prefs.set", &json!({"key": "updateLinks", "value": "automatically"})).unwrap();
|
||
let r = open(&mut s, &doc_path);
|
||
assert_eq!(r["updatedLinks"].as_array().map(Vec::len), Some(1), "{r}");
|
||
assert!(near(centre_colour(&s.doc().unwrap().doc), BLUE));
|
||
}
|
||
|
||
#[test]
|
||
fn relink_points_a_placed_document_at_another_file() {
|
||
let dir = Folder::new("placed-relink");
|
||
let (a, b) = (dir.file("a.vectorcraft"), dir.file("b.vectorcraft"));
|
||
source(&a, 100.0, 50.0, RED, 0);
|
||
source(&b, 100.0, 50.0, BLUE, 0);
|
||
let mut s = session();
|
||
let id = place(&mut s, &a);
|
||
let r = s.execute("links.relink", &json!({"ids": [id.0], "path": b})).unwrap();
|
||
assert_eq!(r["relinked"], json!([id.0]), "{r}");
|
||
assert_eq!(placed(&s, id).link.path, b);
|
||
assert!(near(centre_colour(&s.doc().unwrap().doc), BLUE));
|
||
}
|
||
|
||
#[test]
|
||
fn a_placed_document_inside_a_placed_document_outputs_from_its_own_file() {
|
||
let dir = Folder::new("placed-nested");
|
||
let (logo, card) = (dir.file("logo.vectorcraft"), dir.file("card.vectorcraft"));
|
||
source(&logo, 100.0, 50.0, RED, 0);
|
||
// The card places the logo, and is saved with the logo's preview only.
|
||
let mut s = session();
|
||
place(&mut s, &logo);
|
||
save(&mut s, &card);
|
||
s.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
|
||
place(&mut s, &card);
|
||
assert!(near(centre_colour(&s.doc().unwrap().doc), RED));
|
||
// The logo inside is read from its file: vectors, not its preview.
|
||
let svg = s.execute("document.serialize", &json!({"format": "svg"})).unwrap();
|
||
let text = svg["text"].as_str().unwrap();
|
||
assert!(!text.contains("<image"), "the logo's file, not its preview: {text}");
|
||
assert_eq!(pdf_contents(&decode(&s.execute("document.serialize", &json!({"format": "pdf"})).unwrap())).0, 0);
|
||
}
|
||
|
||
#[test]
|
||
fn documents_that_place_each_other_dont_loop() {
|
||
let dir = Folder::new("placed-loop");
|
||
let (a, b) = (dir.file("a.vectorcraft"), dir.file("b.vectorcraft"));
|
||
source(&a, 100.0, 50.0, RED, 0);
|
||
// B places A; A then places B.
|
||
let mut s = session();
|
||
place(&mut s, &a);
|
||
save(&mut s, &b);
|
||
s.execute("document.open", &json!({"path": a})).unwrap();
|
||
place(&mut s, &b);
|
||
save(&mut s, &a);
|
||
s.execute("document.open", &json!({"path": b})).unwrap();
|
||
let u = s.execute("links.update", &json!({})).unwrap();
|
||
assert_eq!(u["updated"].as_array().map(Vec::len), Some(1), "{u}");
|
||
// Drawn and output a few levels deep, then their previews.
|
||
assert!(near(centre_colour(&s.doc().unwrap().doc), RED));
|
||
for format in ["svg", "pdf", "png"] {
|
||
s.execute("document.serialize", &json!({"format": format})).unwrap();
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn what_cant_be_placed_linked_is_an_error() {
|
||
let dir = Folder::new("placed-errors");
|
||
let src = dir.file("logo.vectorcraft");
|
||
source(&src, 100.0, 50.0, RED, 0);
|
||
let mut s = session();
|
||
let place = |s: &mut Session, p: Value| s.execute("file.place", &p).unwrap_err().to_string();
|
||
let e = place(&mut s, json!({"path": src, "page": 2}));
|
||
assert!(e.contains("page 2") && e.contains("1 artboard"), "{e}");
|
||
let e = place(&mut s, json!({"path": src, "crop": "art"}));
|
||
assert!(e.contains("crop"), "{e}");
|
||
let empty = dir.file("empty.vectorcraft");
|
||
write(&empty, &vectorcraft_format::save_file(&Document::new(100.0, 50.0)));
|
||
let e = place(&mut s, json!({"path": empty, "crop": "bounding"}));
|
||
assert!(e.contains("no art to place"), "{e}");
|
||
// Junk params are errors, not crashes.
|
||
for v in [json!(null), json!(true), json!(-1), json!(1e308), json!("x"), json!([1, 2]), json!({"a": {"b": []}})] {
|
||
for k in ["link", "page", "crop", "at", "rect"] {
|
||
let _ = s.execute("file.place", &json!({"path": src, k: v}));
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn a_placed_documents_resources_stay_its_own() {
|
||
let dir = Folder::new("placed-symbols");
|
||
let src = dir.file("badge.vectorcraft");
|
||
star_source(&src, "star");
|
||
let mut s = session();
|
||
parent_star(&mut s);
|
||
place(&mut s, &src);
|
||
assert!(near(centre_colour(&s.doc().unwrap().doc), BLUE), "the file's own Star");
|
||
let doc = &s.doc().unwrap().doc;
|
||
assert_eq!(doc.symbols.len(), 1, "nothing joins the document");
|
||
let prefix = vectorcraft_doc::placed_document::RESOURCE_PREFIX;
|
||
assert!(doc.images.keys().all(|k| !k.starts_with(prefix)));
|
||
let svg = s.execute("document.serialize", &json!({"format": "svg"})).unwrap();
|
||
assert!(!svg["text"].as_str().unwrap().contains("<image"));
|
||
// Nor its saves.
|
||
let saved = String::from_utf8(decode(&s.execute("document.serialize", &json!({"format": "vectorcraft"})).unwrap())).unwrap();
|
||
assert!(!saved.contains(prefix), "{saved}");
|
||
// Saved for older apps: the art and its resources, as plain objects.
|
||
let old = vectorcraft_format::load(&decode(&s.execute("document.serialize", &json!({"format": "vectorcraft", "version": 2})).unwrap())).unwrap();
|
||
assert!(!old.has_placed());
|
||
assert!(near(centre_colour(&old), BLUE));
|
||
}
|
||
|
||
#[test]
|
||
fn break_link_gives_the_editable_copy_placing_without_link_gives() {
|
||
let dir = Folder::new("placed-break");
|
||
let src = dir.file("badge.vectorcraft");
|
||
star_source(&src, "break");
|
||
let mut s = session();
|
||
parent_star(&mut s);
|
||
// The copy placing without Link gives.
|
||
let r = s.execute("file.place", &json!({"path": src, "link": false, "at": [200, 150]})).unwrap();
|
||
let copy = s.doc().unwrap().doc.node(NodeId(r["ids"][0].as_u64().unwrap())).unwrap().clone();
|
||
s.execute("edit.undo", &json!({})).unwrap();
|
||
let id = place(&mut s, &src);
|
||
let r = s.execute("links.embed", &json!({"ids": [id.0]})).unwrap();
|
||
assert_eq!(r["embedded"], json!([id.0]), "{r}");
|
||
let doc = &s.doc().unwrap().doc;
|
||
let n = doc.node(id).unwrap();
|
||
assert!(!doc.has_placed() && n.kind_label() == "Clip Group", "{}", n.kind_label());
|
||
assert_eq!((n.name.as_deref(), n.geometric_bounds()), (copy.name.as_deref(), copy.geometric_bounds()));
|
||
let kinds = |n: &Node| {
|
||
let mut k = vec![];
|
||
n.walk(&mut |c| k.push(c.kind_label()));
|
||
k
|
||
};
|
||
assert_eq!(kinds(n), kinds(©));
|
||
assert!(near(centre_colour(doc), BLUE), "the art as it showed");
|
||
let names: Vec<&str> = doc.symbols.iter().map(|s| s.name.as_str()).collect();
|
||
assert_eq!(names.len(), 2, "the file's Star joined, renamed: {names:?}");
|
||
assert_eq!(s.execute("links.list", &json!({})).unwrap()["links"], json!([]));
|
||
assert_eq!(s.doc().unwrap().history.undo.last().unwrap().label, "Embed");
|
||
s.execute("edit.undo", &json!({})).unwrap();
|
||
assert!(s.doc().unwrap().doc.has_placed());
|
||
// A file that can't be read stays linked.
|
||
std::fs::remove_file(&src).unwrap();
|
||
let doc_path = dir.file("poster.vectorcraft");
|
||
save(&mut s, &doc_path);
|
||
open(&mut s, &doc_path);
|
||
let r = s.execute("links.embed", &json!({"ids": [id.0]})).unwrap();
|
||
assert_eq!((r["embedded"].clone(), r["missing"].clone()), (json!([]), json!([id.0])), "{r}");
|
||
}
|
||
|
||
#[test]
|
||
fn expand_turns_a_placed_document_into_editable_art_with_its_resources() {
|
||
let dir = Folder::new("placed-expand");
|
||
let src = dir.file("badge.vectorcraft");
|
||
star_source(&src, "star");
|
||
let mut s = session();
|
||
parent_star(&mut s);
|
||
let id = place(&mut s, &src);
|
||
s.execute("object.scale", &json!({"sx": 200})).unwrap();
|
||
let before = bounds(&s, id);
|
||
s.execute("object.expand", &json!({})).unwrap();
|
||
let doc = &s.doc().unwrap().doc;
|
||
let n = doc.node(id).unwrap();
|
||
assert!(n.is_container() && !doc.has_placed(), "{:?}", n.kind_label());
|
||
let after = n.geometric_bounds().unwrap();
|
||
assert!((after.width() - before.width()).abs() < 0.5 && (after.height() - before.height()).abs() < 0.5, "{after:?} vs {before:?}");
|
||
assert!(near(centre_colour(doc), BLUE));
|
||
let saved = vectorcraft_format::save_file(doc);
|
||
assert!(near(centre_colour(&vectorcraft_format::load(&saved).unwrap()), BLUE), "saved with its resources");
|
||
let info = s.execute("object.expand.info", &json!({})).unwrap();
|
||
assert_eq!(info["object"], false, "nothing left to expand: {info}");
|
||
}
|
||
|
||
#[test]
|
||
fn saving_a_document_refreshes_the_open_documents_that_place_it() {
|
||
let dir = Folder::new("placed-refresh");
|
||
let src = dir.file("logo.vectorcraft");
|
||
source(&src, 100.0, 50.0, RED, 0);
|
||
let mut s = session();
|
||
let id = place(&mut s, &src);
|
||
let parent = s.active_index().unwrap();
|
||
// Open the file, recolour it and save it.
|
||
s.execute("document.open", &json!({"path": src})).unwrap();
|
||
s.execute("select.all", &json!({})).unwrap();
|
||
s.execute("paint.setFill", &json!({"color": "#1414e6"})).unwrap();
|
||
s.execute("document.save", &json!({})).unwrap();
|
||
let child = s.active_index().unwrap();
|
||
assert_ne!(parent, child);
|
||
let doc = &s.documents()[parent];
|
||
assert_eq!(doc.history.undo.last().unwrap().label, "Update Links");
|
||
assert!(near(centre_colour(&doc.doc), BLUE), "shows the saved version");
|
||
assert!(matches!(doc.doc.node(id).map(|n| &n.kind), Some(NodeKind::PlacedDocument(_))));
|
||
assert_eq!(s.active_index(), Some(child), "the saved document stays active");
|
||
}
|
||
|
||
#[test]
|
||
fn link_info_gives_the_artboards_size_not_pixels() {
|
||
let dir = Folder::new("placed-info");
|
||
let src = dir.file("logo.vectorcraft");
|
||
source(&src, 100.0, 50.0, RED, 0);
|
||
let mut s = session();
|
||
let id = place(&mut s, &src);
|
||
s.execute("object.scale", &json!({"sx": 200})).unwrap();
|
||
let i = s.execute("links.info", &json!({"id": id.0})).unwrap();
|
||
assert_eq!((i["pageWidth"].as_f64(), i["pageHeight"].as_f64(), i["document"].as_bool()), (Some(100.0), Some(50.0), Some(true)), "{i}");
|
||
assert!(i.get("pixelWidth").is_none() && i.get("ppi").is_none(), "no pixels: {i}");
|
||
assert_eq!(
|
||
(i["status"].as_str(), i["fileName"].as_str(), i["scale"].clone()),
|
||
(Some("ok"), Some("logo.vectorcraft"), json!([200.0, 200.0])),
|
||
"{i}"
|
||
);
|
||
}
|