use std::io::Write;
use zenith_cli::commands::render::{load_data_context, to_png_with_dir, to_scene_json};
use zenith_cli::config::CliPolicyFlags;
fn temp_file(suffix: &str, content: &[u8]) -> (tempfile::TempDir, std::path::PathBuf) {
let dir = tempfile::TempDir::new().expect("tempdir");
let path = dir.path().join(format!("data{suffix}"));
std::fs::File::create(&path)
.expect("create temp file")
.write_all(content)
.expect("write temp file");
(dir, path)
}
const DATA_REF_DOC: &str = r##"zenith version=1 {
project id="proj.dr" name="Data Ref"
tokens format="zenith-token-v1" {}
styles {}
document id="doc.dr" title="Data Ref" {
page id="page.dr" w=(px)100 h=(px)100 background=(data)"c" {
}
}
}
"##;
const PLAIN_DOC: &str = r##"zenith version=1 {
project id="proj.plain" name="Plain"
tokens format="zenith-token-v1" {
token id="color.bg" type="color" value="#f8fafc"
}
styles {}
document id="doc.plain" title="Plain" {
page id="page.plain" w=(px)100 h=(px)100 {
rect id="rect.plain" x=(px)0 y=(px)0 w=(px)100 h=(px)100 fill=(token)"color.bg"
}
}
}
"##;
#[test]
fn load_json_flat_fields_resolve() {
let (_dir, path) = temp_file(".json", br##"{"c": "#ff0000", "name": "red"}"##);
let ctx = load_data_context(&path).expect("load must succeed");
assert_eq!(ctx.get("c"), Some("#ff0000"));
assert_eq!(ctx.get("name"), Some("red"));
}
#[test]
fn load_json_nested_flattens_to_dot_paths() {
let (_dir, path) = temp_file(
".json",
br#"{"revenue": {"total": 42, "tax": 3.5}, "label": "Q1"}"#,
);
let ctx = load_data_context(&path).expect("load must succeed");
assert_eq!(ctx.get("revenue.total"), Some("42"));
assert_eq!(ctx.get("revenue.tax"), Some("3.5"));
assert_eq!(ctx.get("label"), Some("Q1"));
}
#[test]
fn load_json_array_uses_first_element() {
let (_dir, path) = temp_file(".json", br##"[{"c": "#00ff00"}, {"c": "#0000ff"}]"##);
let ctx = load_data_context(&path).expect("load must succeed");
assert_eq!(ctx.get("c"), Some("#00ff00"));
}
#[test]
fn load_json_empty_array_is_error() {
let (_dir, path) = temp_file(".json", b"[]");
assert!(load_data_context(&path).is_err());
}
#[test]
fn load_csv_header_and_first_row() {
let (_dir, path) = temp_file(".csv", b"c,name\n#ff0000,red\n#00ff00,green");
let ctx = load_data_context(&path).expect("load must succeed");
assert_eq!(ctx.get("c"), Some("#ff0000"));
assert_eq!(ctx.get("name"), Some("red"));
}
#[test]
fn load_csv_no_data_rows_is_error() {
let (_dir, path) = temp_file(".csv", b"c,name\n");
assert!(load_data_context(&path).is_err());
}
#[test]
fn load_unknown_extension_is_error() {
let (_dir, path) = temp_file(".xml", b"<data/>");
let err = load_data_context(&path).unwrap_err();
assert!(
err.message.contains("unsupported file extension"),
"got: {}",
err.message
);
}
#[test]
fn render_with_data_ctx_succeeds() {
let (_dir, data_path) = temp_file(".json", br##"{"c": "#ff0000"}"##);
let ctx = load_data_context(&data_path).expect("load");
let artifact = to_png_with_dir(
DATA_REF_DOC,
None,
1,
false,
&CliPolicyFlags::default(),
Some(&ctx),
)
.expect("render must succeed");
assert!(
artifact.png.starts_with(&[0x89, 0x50, 0x4E, 0x47]),
"output must be a PNG"
);
}
#[test]
fn render_without_data_ctx_is_non_fatal() {
let result = to_png_with_dir(
DATA_REF_DOC,
None,
1,
false,
&CliPolicyFlags::default(),
None,
);
assert!(
result.is_ok(),
"render without data ctx must succeed (non-fatal advisories); got: {:?}",
result.err().map(|e| e.message)
);
}
#[test]
fn plain_doc_byte_identical_with_and_without_data() {
let (_dir, data_path) = temp_file(".json", br#"{"irrelevant": "value"}"#);
let ctx = load_data_context(&data_path).expect("load");
let png_without = to_png_with_dir(PLAIN_DOC, None, 1, false, &CliPolicyFlags::default(), None)
.expect("render without data must succeed")
.png;
let png_with = to_png_with_dir(
PLAIN_DOC,
None,
1,
false,
&CliPolicyFlags::default(),
Some(&ctx),
)
.expect("render with irrelevant data must succeed")
.png;
assert_eq!(
png_without, png_with,
"a doc with no data refs must render byte-identically with or without a data context"
);
}
#[test]
fn scene_json_with_data_ctx_succeeds() {
let (_dir, data_path) = temp_file(".json", br##"{"c": "#0000ff"}"##);
let ctx = load_data_context(&data_path).expect("load");
let artifact = to_scene_json(
DATA_REF_DOC,
None,
1,
&CliPolicyFlags::default(),
Some(&ctx),
)
.expect("scene JSON must succeed with data ctx");
assert!(
artifact.json.contains("zenith-scene-v1"),
"scene JSON must contain schema field"
);
let hard: Vec<_> = artifact
.diagnostics
.iter()
.filter(|d| d.severity == zenith_core::Severity::Error)
.collect();
assert!(
hard.is_empty(),
"no hard errors expected when data ctx is provided; got: {:?}",
hard
);
}