mod common;
use common::*;
use zenith_core::{Node, PropertyValue};
use zenith_tx::{Op, Permissions, Transaction, TxStatus, run_transaction};
fn pv_value(pv: &PropertyValue) -> Option<f64> {
match pv {
PropertyValue::Dimension(d) => Some(d.value),
_ => None,
}
}
const GRID_PATTERN_DOC: &str = r##"zenith version=1 {
project id="proj" name="Test"
tokens format="zenith-token-v1" {
token id="color.dot" type="color" value="#334155"
}
styles { }
document id="doc1" title="T" {
page id="pg1" w=(px)400 h=(px)300 {
pattern id="dots" kind="grid" x=(px)0 y=(px)0 w=(px)100 h=(px)100 spacing=(px)50 fill=(token)"color.dot" {
ellipse id="dot" x=(px)0 y=(px)0 w=(px)10 h=(px)10 fill=(token)"color.dot"
}
}
}
}"##;
const PLAIN_RECT_DOC: &str = r##"zenith version=1 {
project id="proj" name="Test"
tokens format="zenith-token-v1" { }
styles { }
document id="doc1" title="T" {
page id="pg1" w=(px)400 h=(px)300 {
rect id="box" x=(px)0 y=(px)0 w=(px)100 h=(px)100
}
}
}"##;
#[test]
fn detach_pattern_grid_accepted() {
let doc = parse(GRID_PATTERN_DOC);
let tx = Transaction {
ops: vec![Op::DetachPattern {
node: "dots".to_owned(),
}],
permissions: Permissions::default(),
};
let result = run_transaction(&doc, &tx).expect("run_transaction should not error");
assert_eq!(
result.status,
TxStatus::Accepted,
"expected Accepted; diagnostics: {:?}",
result.diagnostics
);
assert_eq!(
result.affected_node_ids,
vec!["dots".to_owned()],
"affected must contain the detached pattern id"
);
let after_doc = parse(&result.source_after);
let page = after_doc
.body
.pages
.first()
.expect("page pg1 must exist after detach");
let group = page
.children
.iter()
.find_map(|n| match n {
Node::Group(g) if g.id == "dots" => Some(g),
_ => None,
})
.expect("a group with id 'dots' must replace the pattern");
assert!(
page.children.iter().all(|n| !matches!(n, Node::Pattern(_))),
"no pattern node may remain after detach"
);
assert_eq!(
group.x.as_ref().and_then(pv_value),
Some(0.0),
"group x must equal pattern bounds x"
);
assert_eq!(
group.y.as_ref().and_then(pv_value),
Some(0.0),
"group y must equal pattern bounds y"
);
assert_eq!(
group.w.as_ref().and_then(pv_value),
Some(100.0),
"group w must equal pattern bounds w"
);
assert_eq!(
group.h.as_ref().and_then(pv_value),
Some(100.0),
"group h must equal pattern bounds h"
);
assert_eq!(
group.children.len(),
4,
"grid 100/50 must yield 4 instances"
);
let positions: Vec<(String, f64, f64)> = group
.children
.iter()
.map(|child| match child {
Node::Ellipse(e) => (
e.id.clone(),
e.x.as_ref().and_then(pv_value).unwrap_or(f64::NAN),
e.y.as_ref().and_then(pv_value).unwrap_or(f64::NAN),
),
other => panic!("expected ellipse clone, got {other:?}"),
})
.collect();
let expected = [
("dots.0", 0.0, 0.0),
("dots.1", 50.0, 0.0),
("dots.2", 0.0, 50.0),
("dots.3", 50.0, 50.0),
];
for (i, (eid, ex, ey)) in expected.iter().enumerate() {
let (gid, gx, gy) = &positions[i];
assert_eq!(gid, eid, "child {i} id mismatch");
assert_eq!(gx, ex, "child {i} x mismatch");
assert_eq!(gy, ey, "child {i} y mismatch");
}
}
#[test]
fn detach_pattern_grid_revalidates_clean() {
let doc = parse(GRID_PATTERN_DOC);
let tx = Transaction {
ops: vec![Op::DetachPattern {
node: "dots".to_owned(),
}],
permissions: Permissions::default(),
};
let result = run_transaction(&doc, &tx).expect("run_transaction should not error");
assert_ne!(
result.status,
TxStatus::Rejected,
"detach must not be rejected; diagnostics: {:?}",
result.diagnostics
);
assert!(
result.diagnostics.iter().all(|d| d.code != "id.duplicate"),
"child ids must be unique (no id.duplicate); got: {:?}",
result.diagnostics
);
}
#[test]
fn detach_pattern_unknown_node_rejected() {
let doc = parse(GRID_PATTERN_DOC);
let tx = Transaction {
ops: vec![Op::DetachPattern {
node: "does_not_exist".to_owned(),
}],
permissions: Permissions::default(),
};
let result = run_transaction(&doc, &tx).expect("run_transaction should not error");
assert_eq!(
result.status,
TxStatus::Rejected,
"expected Rejected; diagnostics: {:?}",
result.diagnostics
);
assert!(
result
.diagnostics
.iter()
.any(|d| d.code == "tx.unknown_node"),
"expected tx.unknown_node; got: {:?}",
result.diagnostics
);
assert_eq!(result.source_after, result.source_before);
}
#[test]
fn detach_pattern_not_a_pattern_rejected() {
let doc = parse(PLAIN_RECT_DOC);
let tx = Transaction {
ops: vec![Op::DetachPattern {
node: "box".to_owned(),
}],
permissions: Permissions::default(),
};
let result = run_transaction(&doc, &tx).expect("run_transaction should not error");
assert_eq!(
result.status,
TxStatus::Rejected,
"expected Rejected; diagnostics: {:?}",
result.diagnostics
);
assert!(
result
.diagnostics
.iter()
.any(|d| d.code == "tx.not_a_pattern"),
"expected tx.not_a_pattern; got: {:?}",
result.diagnostics
);
assert_eq!(result.source_after, result.source_before);
}