mod common;
use common::*;
fn parse_and_validate(src: &str) -> ValidationReport {
let adapter = KdlAdapter;
let doc = adapter.parse(src.as_bytes()).expect("parse must succeed");
validate(&doc)
}
#[test]
fn valid_recipes_block_is_clean() {
let src = r##"zenith version=1 {
project id="proj.rc" name="RC"
tokens format="zenith-token-v1" {
token id="color.brand" type="color" value="#001f3f"
}
styles {
}
recipes {
recipe id="recipe.aurora" kind="aurora" bounds="page.main" {
palette token="color.brand"
expanded node="blob.a"
}
}
document id="doc.rc" title="RC" {
page id="page.main" w=(px)1920 h=(px)1080 {
rect id="blob.a" x=(px)0 y=(px)0 w=(px)100 h=(px)100
}
}
}
"##;
let report = parse_and_validate(src);
let recipe_codes: Vec<&str> = report
.diagnostics
.iter()
.filter(|d| d.code.starts_with("recipe."))
.map(|d| d.code.as_str())
.collect();
assert!(
recipe_codes.is_empty(),
"clean recipes block must produce no recipe.* diagnostics; got {:?}",
recipe_codes
);
}
#[test]
fn palette_reference_counts_as_token_usage() {
let src = r##"zenith version=1 {
project id="proj.use" name="USE"
tokens format="zenith-token-v1" {
token id="color.only.palette" type="color" value="#abcdef"
}
styles {
}
recipes {
recipe id="recipe.p" kind="aurora" {
palette token="color.only.palette"
}
}
document id="doc.use" title="USE" {
page id="page.use" w=(px)400 h=(px)300 {
rect id="r" x=(px)0 y=(px)0 w=(px)50 h=(px)50
}
}
}
"##;
let report = parse_and_validate(src);
let unused: Vec<&str> = report
.diagnostics
.iter()
.filter(|d| d.code == "token.unused")
.map(|d| d.message.as_str())
.collect();
assert!(
unused.is_empty(),
"a token referenced only by a recipe palette must not be token.unused; got {:?}",
unused
);
}
#[test]
fn valid_recipes_bounds_node_id_is_clean() {
let src = r##"zenith version=1 {
project id="proj.bn" name="BN"
tokens format="zenith-token-v1" {
}
styles {
}
recipes {
recipe id="recipe.r" kind="scatter" bounds="frame.container" {
}
}
document id="doc.bn" title="BN" {
page id="page.main" w=(px)1920 h=(px)1080 {
frame id="frame.container" x=(px)0 y=(px)0 w=(px)400 h=(px)300 {
}
}
}
}
"##;
let report = parse_and_validate(src);
let recipe_codes: Vec<&str> = report
.diagnostics
.iter()
.filter(|d| d.code.starts_with("recipe."))
.map(|d| d.code.as_str())
.collect();
assert!(
recipe_codes.is_empty(),
"bounds naming a real node id must produce no recipe.* diagnostics; got {:?}",
recipe_codes
);
}
#[test]
fn duplicate_recipe_id_is_error() {
let src = r##"zenith version=1 {
project id="proj.dup" name="DUP"
tokens format="zenith-token-v1" {
}
styles {
}
recipes {
recipe id="recipe.a" kind="aurora" {
}
recipe id="recipe.a" kind="scatter" {
}
}
document id="doc.dup" title="DUP" {
page id="page.main" w=(px)1920 h=(px)1080 {
}
}
}
"##;
let report = parse_and_validate(src);
assert!(
has_code(&report, "recipe.duplicate_id"),
"duplicate recipe id must produce recipe.duplicate_id; got {:?}",
codes(&report)
);
}
#[test]
fn palette_referencing_undeclared_token_is_error() {
let src = r##"zenith version=1 {
project id="proj.pt" name="PT"
tokens format="zenith-token-v1" {
}
styles {
}
recipes {
recipe id="recipe.a" kind="aurora" {
palette token="color.missing"
}
}
document id="doc.pt" title="PT" {
page id="page.main" w=(px)1920 h=(px)1080 {
}
}
}
"##;
let report = parse_and_validate(src);
assert!(
has_code(&report, "recipe.unknown_palette_token"),
"palette referencing undeclared token must produce recipe.unknown_palette_token; got {:?}",
codes(&report)
);
}
#[test]
fn palette_referencing_non_color_token_is_error() {
let src = r##"zenith version=1 {
project id="proj.nc" name="NC"
tokens format="zenith-token-v1" {
token id="dim.spacing" type="dimension" value=(px)8
}
styles {
}
recipes {
recipe id="recipe.a" kind="aurora" {
palette token="dim.spacing"
}
}
document id="doc.nc" title="NC" {
page id="page.main" w=(px)1920 h=(px)1080 {
}
}
}
"##;
let report = parse_and_validate(src);
assert!(
has_code(&report, "recipe.unknown_palette_token"),
"palette referencing non-color token must produce recipe.unknown_palette_token; got {:?}",
codes(&report)
);
}
#[test]
fn expanded_referencing_absent_node_is_error() {
let src = r##"zenith version=1 {
project id="proj.en" name="EN"
tokens format="zenith-token-v1" {
}
styles {
}
recipes {
recipe id="recipe.a" kind="aurora" {
expanded node="ghost.node"
}
}
document id="doc.en" title="EN" {
page id="page.main" w=(px)1920 h=(px)1080 {
rect id="real.node" x=(px)0 y=(px)0 w=(px)100 h=(px)100
}
}
}
"##;
let report = parse_and_validate(src);
assert!(
has_code(&report, "recipe.unknown_expanded_node"),
"expanded referencing absent node must produce recipe.unknown_expanded_node; got {:?}",
codes(&report)
);
}
#[test]
fn unknown_bounds_id_is_error() {
let src = r##"zenith version=1 {
project id="proj.ub" name="UB"
tokens format="zenith-token-v1" {
}
styles {
}
recipes {
recipe id="recipe.a" kind="aurora" bounds="frame.nowhere" {
}
}
document id="doc.ub" title="UB" {
page id="page.main" w=(px)1920 h=(px)1080 {
}
}
}
"##;
let report = parse_and_validate(src);
assert!(
has_code(&report, "recipe.unknown_bounds"),
"bounds naming unknown id must produce recipe.unknown_bounds; got {:?}",
codes(&report)
);
}
#[test]
fn bounds_naming_real_page_is_clean() {
let src = r##"zenith version=1 {
project id="proj.bp" name="BP"
tokens format="zenith-token-v1" {
}
styles {
}
recipes {
recipe id="recipe.a" kind="aurora" bounds="page.main" {
}
}
document id="doc.bp" title="BP" {
page id="page.main" w=(px)1920 h=(px)1080 {
}
}
}
"##;
let report = parse_and_validate(src);
assert!(
!has_code(&report, "recipe.unknown_bounds"),
"bounds naming a real page id must not produce recipe.unknown_bounds; got {:?}",
codes(&report)
);
}