use renderreport::prelude::*;
const CONTENT_MARKER: &str = "// Report Content";
fn canonical_request(engine: &Engine) -> renderreport::RenderRequest {
engine
.report("default")
.title("Typ Validation")
.add_component(Section::new("Heading").with_level(2))
.add_component(ScoreCard::new("Score", 85))
.add_component(Callout::info("Validation test content."))
.build()
}
fn content_section(source: &str) -> &str {
source
.split_once(CONTENT_MARKER)
.map(|(_, rest)| rest)
.unwrap_or(source)
}
fn strip_strings_and_comments(source: &str) -> String {
let bytes = source.as_bytes();
let mut out = String::with_capacity(source.len());
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
if b == b'/' && i + 1 < bytes.len() && bytes[i + 1] == b'/' {
while i < bytes.len() && bytes[i] != b'\n' {
i += 1;
}
continue;
}
if b == b'/' && i + 1 < bytes.len() && bytes[i + 1] == b'*' {
i += 2;
while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {
i += 1;
}
i = (i + 2).min(bytes.len());
continue;
}
if b == b'"' {
out.push('"');
i += 1;
while i < bytes.len() {
let c = bytes[i];
if c == b'\\' && i + 1 < bytes.len() {
i += 2;
continue;
}
if c == b'"' {
i += 1;
break;
}
i += 1;
}
out.push('"');
continue;
}
out.push(b as char);
i += 1;
}
out
}
#[test]
fn render_typ_returns_non_empty_source() {
let engine = Engine::new().expect("Engine::new failed");
let request = canonical_request(&engine);
let source = engine.render_typ(&request).expect("render_typ failed");
assert!(!source.is_empty(), "render_typ produced empty source");
assert!(
source.contains(CONTENT_MARKER),
"source missing '// Report Content' marker — generator structure changed"
);
}
#[test]
fn generated_source_compiles_to_pdf() {
let engine = Engine::new().expect("Engine::new failed");
let request = canonical_request(&engine);
let _source = engine.render_typ(&request).expect("render_typ failed");
let pdf = engine
.render_pdf(&request)
.expect("render_pdf failed on canonical request");
assert!(!pdf.is_empty(), "render_pdf produced empty bytes");
}
#[test]
fn bracket_counts_are_balanced_in_generated_source() {
let engine = Engine::new().expect("Engine::new failed");
let request = canonical_request(&engine);
let source = engine.render_typ(&request).expect("render_typ failed");
let mut square = 0i32;
let mut curly = 0i32;
let mut paren = 0i32;
for ch in source.chars() {
match ch {
'[' => square += 1,
']' => square -= 1,
'{' => curly += 1,
'}' => curly -= 1,
'(' => paren += 1,
')' => paren -= 1,
_ => {}
}
}
assert_eq!(square, 0, "'[' / ']' count imbalance: net {square}");
assert_eq!(curly, 0, "'{{' / '}}' count imbalance: net {curly}");
assert_eq!(paren, 0, "'(' / ')' count imbalance: net {paren}");
}
#[test]
fn no_obvious_renderer_leaks_in_content_section() {
let engine = Engine::new().expect("Engine::new failed");
let request = canonical_request(&engine);
let source = engine.render_typ(&request).expect("render_typ failed");
let content = content_section(&source);
let stripped = strip_strings_and_comments(content);
let tokens = ["block", "box", "grid", "stack", "rect", "circle"];
for line in stripped.lines() {
let trimmed = line.trim_start();
for tok in tokens {
let pat = format!("{tok}(");
if let Some(pos) = trimmed.find(&pat) {
let left = &trimmed[..pos];
let last = left.chars().last();
let preceded_ok = match last {
None => false, Some(c) => {
c == '#'
|| c.is_alphanumeric()
|| c == '_'
|| c == '-'
|| matches!(c, ',' | ':' | '(' | '+' | '*' | '/' | '=' | '>' | '<' | '&' | '|')
|| c.is_whitespace()
}
};
assert!(
preceded_ok,
"possible renderer leak: '{pat}' appears in content without code escape\nline: {line}"
);
}
}
}
}
#[test]
fn typst_syntax_in_user_data_does_not_break_compile() {
let engine = Engine::new().expect("Engine::new failed");
let request = engine
.report("default")
.title("Hello #v(10em) [world] \"quoted\" *bold*")
.add_component(Section::new("Heading with #block(fill: red)[x]").with_level(2))
.add_component(Callout::info("Body with `code` and @ref and $math$."))
.build();
let source = engine
.render_typ(&request)
.expect("render_typ failed on poisoned input");
assert!(source.contains(CONTENT_MARKER));
let pdf = engine
.render_pdf(&request)
.expect("compile failed: user-supplied Typst syntax broke generation");
assert!(!pdf.is_empty());
}