mod common;
use common::*;
use zenith_core::default_provider;
use zenith_scene::ir::SceneCommand;
fn glyph_run_rows(result: &CompileResult) -> Vec<(f64, f64, f32)> {
result
.scene
.commands
.iter()
.filter_map(|c| match c {
SceneCommand::DrawGlyphRun {
x, y, font_size, ..
} => Some((*x, *y, *font_size)),
_ => None,
})
.collect()
}
#[test]
fn multi_block_stacks_with_styled_heading_and_separated_paragraphs() {
let src = r##"zenith version=1 {
project id="proj.mb" name="MB"
tokens format="zenith-token-v1" {
token id="color.ink" type="color" value="#111827"
token id="font.body" type="fontFamily" value="Noto Sans"
token id="size.body" type="dimension" value=(px)20
token id="size.h1" type="dimension" value=(px)40
}
styles {}
document id="doc.mb" title="MB" {
page id="page.mb" w=(px)600 h=(px)400 {
text id="t.mb" x=(px)20 y=(px)20 w=(px)560 fill=(token)"color.ink" font-family=(token)"font.body" font-size=(token)"size.body" format="markdown" {
block role="h1" font-size=(token)"size.h1"
span "# Title\n\nFirst paragraph here.\n\nSecond paragraph here."
}
}
}
}"##;
let doc = parse(src);
let result = compile(&doc, &default_provider());
assert!(
result
.diagnostics
.iter()
.all(|d| d.severity != zenith_core::Severity::Error),
"expected no errors; got: {:?}",
result.diagnostics
);
let rows = glyph_run_rows(&result);
assert!(
rows.len() >= 3,
"expected >= 3 glyph runs (heading + 2 paragraphs); got {}",
rows.len()
);
let (_, heading_y, heading_fs) = rows[0];
assert!(
(heading_fs - 40.0).abs() < 0.5,
"heading run must use the h1 font-size 40; got {heading_fs}"
);
let body_runs: Vec<_> = rows
.iter()
.filter(|(_, _, fs)| (*fs - 20.0).abs() < 0.5)
.collect();
assert!(
body_runs.len() >= 2,
"expected >= 2 body-size paragraph runs; got {}",
body_runs.len()
);
let first_p_y = body_runs[0].1;
let second_p_y = body_runs[body_runs.len() - 1].1;
assert!(
heading_y < first_p_y,
"heading (y={heading_y}) must sit above first paragraph (y={first_p_y})"
);
assert!(
first_p_y < second_p_y,
"first paragraph (y={first_p_y}) must sit above second paragraph (y={second_p_y})"
);
}
#[test]
fn code_block_emits_background_rect_and_glyphs() {
let src = r##"zenith version=1 {
project id="proj.cb" name="CB"
tokens format="zenith-token-v1" {
token id="color.ink" type="color" value="#111827"
token id="font.body" type="fontFamily" value="Noto Sans"
token id="size.body" type="dimension" value=(px)18
}
styles {}
document id="doc.cb" title="CB" {
page id="page.cb" w=(px)600 h=(px)400 {
text id="t.cb" x=(px)20 y=(px)20 w=(px)560 fill=(token)"color.ink" font-family=(token)"font.body" font-size=(token)"size.body" format="markdown" {
span "```\nlet x = 1;\n```"
}
}
}
}"##;
let doc = parse(src);
let result = compile(&doc, &default_provider());
assert!(
result
.diagnostics
.iter()
.all(|d| d.severity != zenith_core::Severity::Error),
"expected no errors; got: {:?}",
result.diagnostics
);
let rects = fill_rects(&result);
assert!(
!rects.is_empty(),
"code block must emit at least one background FillRect"
);
let glyph_runs = glyph_run_rows(&result);
assert!(
!glyph_runs.is_empty(),
"code block must emit mono glyph runs for its content"
);
}
#[test]
fn horizontal_rule_emits_rule_fill_rect() {
let src = r##"zenith version=1 {
project id="proj.hr" name="HR"
tokens format="zenith-token-v1" {
token id="color.ink" type="color" value="#111827"
token id="font.body" type="fontFamily" value="Noto Sans"
token id="size.body" type="dimension" value=(px)18
}
styles {}
document id="doc.hr" title="HR" {
page id="page.hr" w=(px)600 h=(px)400 {
text id="t.hr" x=(px)20 y=(px)20 w=(px)560 fill=(token)"color.ink" font-family=(token)"font.body" font-size=(token)"size.body" format="markdown" {
span "Above\n\n---\n\nBelow"
}
}
}
}"##;
let doc = parse(src);
let result = compile(&doc, &default_provider());
assert!(
result
.diagnostics
.iter()
.all(|d| d.severity != zenith_core::Severity::Error),
"expected no errors; got: {:?}",
result.diagnostics
);
let rects = fill_rects(&result);
let thin_full_width = rects.iter().any(|(x, _, w, h)| {
(*x - 20.0).abs() < 1.0 && (*w - 560.0).abs() < 1.0 && *h <= 4.0 && *h > 0.0
});
assert!(
thin_full_width,
"horizontal rule must emit a thin full-width FillRect; rects: {rects:?}"
);
}
#[test]
fn markdown_overflow_short_box_emits_overflow_warning() {
let src = r##"zenith version=1 {
project id="proj.ov1" name="OV1"
tokens format="zenith-token-v1" {
token id="color.ink" type="color" value="#111827"
token id="font.body" type="fontFamily" value="Noto Sans"
token id="size.body" type="dimension" value=(px)18
}
styles {}
document id="doc.ov1" title="OV1" {
page id="page.ov1" w=(px)600 h=(px)400 {
text id="t.ov1" x=(px)20 y=(px)20 w=(px)560 h=(px)30 fill=(token)"color.ink" font-family=(token)"font.body" font-size=(token)"size.body" format="markdown" {
span "First paragraph of text here.\n\nSecond paragraph of text here."
}
}
}
}"##;
let doc = parse(src);
let result = compile(&doc, &default_provider());
let overflow_warns: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "text.overflow")
.collect();
assert_eq!(
overflow_warns.len(),
1,
"expected exactly one text.overflow warning for a short-box markdown node; got: {:?}",
result.diagnostics
);
assert_eq!(
overflow_warns[0].severity,
zenith_core::Severity::Warning,
"text.overflow must be Warning severity, not a hard error"
);
assert!(
overflow_warns[0]
.subject_id
.as_deref()
.map(|s| s.contains("t.ov1"))
.unwrap_or(false),
"subject_id must reference the overflowing text node; got {:?}",
overflow_warns[0].subject_id
);
assert!(
result
.scene
.commands
.iter()
.any(|c| matches!(c, SceneCommand::DrawGlyphRun { .. })),
"glyph runs must still be emitted even when markdown content overflows"
);
}
#[test]
fn markdown_overflow_tall_box_no_warning() {
let src = r##"zenith version=1 {
project id="proj.ov2" name="OV2"
tokens format="zenith-token-v1" {
token id="color.ink" type="color" value="#111827"
token id="font.body" type="fontFamily" value="Noto Sans"
token id="size.body" type="dimension" value=(px)18
}
styles {}
document id="doc.ov2" title="OV2" {
page id="page.ov2" w=(px)600 h=(px)800 {
text id="t.ov2" x=(px)20 y=(px)20 w=(px)560 h=(px)600 fill=(token)"color.ink" font-family=(token)"font.body" font-size=(token)"size.body" format="markdown" {
span "Short paragraph."
}
}
}
}"##;
let doc = parse(src);
let result = compile(&doc, &default_provider());
let overflow_warns: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "text.overflow")
.collect();
assert!(
overflow_warns.is_empty(),
"markdown content that fits its box must not emit text.overflow; got: {:?}",
overflow_warns
);
}
#[test]
fn markdown_overflow_visible_still_warns() {
let src = r##"zenith version=1 {
project id="proj.ov3" name="OV3"
tokens format="zenith-token-v1" {
token id="color.ink" type="color" value="#111827"
token id="font.body" type="fontFamily" value="Noto Sans"
token id="size.body" type="dimension" value=(px)18
}
styles {}
document id="doc.ov3" title="OV3" {
page id="page.ov3" w=(px)600 h=(px)400 {
text id="t.ov3" x=(px)20 y=(px)20 w=(px)560 h=(px)30 overflow="visible" fill=(token)"color.ink" font-family=(token)"font.body" font-size=(token)"size.body" format="markdown" {
span "First paragraph.\n\nSecond paragraph.\n\nThird paragraph."
}
}
}
}"##;
let doc = parse(src);
let result = compile(&doc, &default_provider());
let overflow_warns: Vec<_> = result
.diagnostics
.iter()
.filter(|d| d.code == "text.overflow")
.collect();
assert_eq!(
overflow_warns.len(),
1,
"overflow=\"visible\" markdown node that exceeds box must still emit text.overflow warning; got: {:?}",
result.diagnostics
);
assert_eq!(
overflow_warns[0].severity,
zenith_core::Severity::Warning,
"must be Warning, not an error"
);
}
#[test]
fn single_paragraph_no_block_decls_matches_plain_text() {
let body = "Just a single ordinary paragraph of text that wraps a little.";
let make = |fmt: &str| {
format!(
r##"zenith version=1 {{
project id="proj.bi" name="BI"
tokens format="zenith-token-v1" {{
token id="color.ink" type="color" value="#111827"
token id="font.body" type="fontFamily" value="Noto Sans"
token id="size.body" type="dimension" value=(px)22
}}
styles {{}}
document id="doc.bi" title="BI" {{
page id="page.bi" w=(px)400 h=(px)300 {{
text id="t.bi" x=(px)15 y=(px)25 w=(px)360 fill=(token)"color.ink" font-family=(token)"font.body" font-size=(token)"size.body"{fmt} {{
span "{body}"
}}
}}
}}
}}"##
)
};
let md = compile(&parse(&make(r#" format="markdown""#)), &default_provider());
let plain = compile(&parse(&make("")), &default_provider());
let md_rows = glyph_run_rows(&md);
let plain_rows = glyph_run_rows(&plain);
assert_eq!(
md_rows, plain_rows,
"single-paragraph markdown block layout must match the plain-text glyph stream"
);
}