#![cfg_attr(test, allow(clippy::unwrap_used))]
mod document;
mod extract;
mod slot;
mod template_invariant;
pub use document::wrap_document;
pub use extract::extract_template;
pub use slot::sentinel_to_slots;
pub use template_invariant::{TemplateInvariantViolation, check_template_invariants};
pub mod ctr_check;
pub mod slot_paths;
pub mod slot_warning;
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct Axis {
pub path: String,
pub kind: String,
pub values: Vec<serde_json::Value>,
}
#[derive(Debug, Clone)]
pub struct ViteDevInfo {
pub origin: String,
pub entry: String,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn make_axis(path: &str, kind: &str, values: Vec<serde_json::Value>) -> Axis {
Axis { path: path.to_string(), kind: kind.to_string(), values }
}
#[test]
fn full_pipeline_snapshot() {
let sentinel_html =
r#"<div><h1>%%SEAM:user.name%%</h1><p>%%SEAM:user.email%%</p> <span>Has avatar</span></div>"#;
let nulled_html = r#"<div><h1>%%SEAM:user.name%%</h1><p>%%SEAM:user.email%%</p></div>"#;
let slotted = sentinel_to_slots(sentinel_html);
assert_eq!(
slotted,
r#"<div><h1><!--seam:user.name--></h1><p><!--seam:user.email--></p> <span>Has avatar</span></div>"#
);
let nulled_slotted = sentinel_to_slots(nulled_html);
let axes = vec![make_axis("user.avatar", "nullable", vec![json!("present"), json!(null)])];
let variants = vec![slotted, nulled_slotted];
let template = extract_template(&axes, &variants);
assert!(template.contains("<!--seam:if:user.avatar-->"));
assert!(template.contains("<!--seam:endif:user.avatar-->"));
assert!(template.contains("<span>Has avatar</span>"));
let doc =
wrap_document(&template, &["app.css".into()], &["app.js".into()], false, None, "__seam");
assert!(doc.starts_with("<!DOCTYPE html>"));
assert!(doc.contains("__seam"));
assert!(doc.contains("<!--seam:user.name-->"));
assert!(doc.contains("<!--seam:if:user.avatar-->"));
assert!(doc.contains("app.css"));
assert!(doc.contains("app.js"));
}
#[test]
fn attribute_and_text_mixed_pipeline() {
let html = r#"<div><a href="%%SEAM:link.url%%">%%SEAM:link.text%%</a></div>"#;
let result = sentinel_to_slots(html);
let doc = wrap_document(&result, &[], &[], false, None, "__seam");
assert!(doc.contains("<!--seam:link.url:attr:href-->"));
assert!(doc.contains("<!--seam:link.text-->"));
assert!(!doc.contains("%%SEAM:"));
}
#[test]
fn float_metadata_stays_in_body_with_wrap_document() {
let html = r#"<title>%%SEAM:t%%</title><meta name="desc" content="%%SEAM:d%%"><link rel="canonical" href="%%SEAM:u%%"><div><p>%%SEAM:body%%</p></div>"#;
let slotted = sentinel_to_slots(html);
assert!(slotted.contains("<!--seam:t-->"));
assert!(slotted.contains("<!--seam:d:attr:content-->"));
assert!(slotted.contains("<!--seam:u:attr:href-->"));
let doc =
wrap_document(&slotted, &["style.css".into()], &["app.js".into()], false, None, "__seam");
assert!(doc.starts_with("<!DOCTYPE html>"));
let root = &doc[doc.find("__seam").unwrap()..];
assert!(root.contains("<!--seam:t-->"), "title slot in body");
assert!(root.contains("<!--seam:d:attr:content-->"), "meta slot in body");
assert!(root.contains("<!--seam:body-->"), "body content in body");
}
#[test]
fn float_metadata_with_boolean_axis() {
let with_meta = sentinel_to_slots(
r#"<title>%%SEAM:t%%</title><meta name="og" content="%%SEAM:og%%"><p>%%SEAM:body%%</p>"#,
);
let without_meta = sentinel_to_slots(r#"<title>%%SEAM:t%%</title><p>%%SEAM:body%%</p>"#);
let axes = vec![make_axis("og", "nullable", vec![json!("present"), json!(null)])];
let variants = vec![with_meta, without_meta];
let template = extract_template(&axes, &variants);
assert!(template.contains("<!--seam:if:og-->"));
assert!(template.contains("<!--seam:endif:og-->"));
assert!(template.contains("<!--seam:t-->"));
assert!(template.contains("<!--seam:body-->"));
let doc = wrap_document(&template, &[], &[], false, None, "__seam");
let root = &doc[doc.find("__seam").unwrap()..];
assert!(root.contains("<!--seam:t-->"), "title slot in body");
assert!(root.contains("<!--seam:if:og-->"), "conditional in body");
}
}