1#![cfg_attr(test, allow(clippy::unwrap_used))]
3
4mod document;
5mod extract;
6mod slot;
7mod template_invariant;
8
9pub use document::wrap_document;
10pub use extract::extract_template;
11pub use slot::sentinel_to_slots;
12pub use template_invariant::{TemplateInvariantViolation, check_template_invariants};
13
14pub mod ctr_check;
15pub mod slot_paths;
16pub mod slot_warning;
17
18use serde::Deserialize;
19
20#[derive(Debug, Clone, Deserialize)]
22pub struct Axis {
23 pub path: String,
24 pub kind: String,
25 pub values: Vec<serde_json::Value>,
26}
27
28#[derive(Debug, Clone)]
31pub struct ViteDevInfo {
32 pub origin: String,
33 pub entry: String,
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39 use serde_json::json;
40
41 fn make_axis(path: &str, kind: &str, values: Vec<serde_json::Value>) -> Axis {
44 Axis { path: path.to_string(), kind: kind.to_string(), values }
45 }
46
47 #[test]
48 fn full_pipeline_snapshot() {
49 let sentinel_html =
50 r#"<div><h1>%%SEAM:user.name%%</h1><p>%%SEAM:user.email%%</p> <span>Has avatar</span></div>"#;
51 let nulled_html = r#"<div><h1>%%SEAM:user.name%%</h1><p>%%SEAM:user.email%%</p></div>"#;
52
53 let slotted = sentinel_to_slots(sentinel_html);
55 assert_eq!(
56 slotted,
57 r#"<div><h1><!--seam:user.name--></h1><p><!--seam:user.email--></p> <span>Has avatar</span></div>"#
58 );
59
60 let nulled_slotted = sentinel_to_slots(nulled_html);
61
62 let axes = vec![make_axis("user.avatar", "nullable", vec![json!("present"), json!(null)])];
64 let variants = vec![slotted, nulled_slotted];
65 let template = extract_template(&axes, &variants);
66 assert!(template.contains("<!--seam:if:user.avatar-->"));
67 assert!(template.contains("<!--seam:endif:user.avatar-->"));
68 assert!(template.contains("<span>Has avatar</span>"));
69
70 let doc =
72 wrap_document(&template, &["app.css".into()], &["app.js".into()], false, None, "__seam");
73 assert!(doc.starts_with("<!DOCTYPE html>"));
74 assert!(doc.contains("__seam"));
75 assert!(doc.contains("<!--seam:user.name-->"));
76 assert!(doc.contains("<!--seam:if:user.avatar-->"));
77 assert!(doc.contains("app.css"));
78 assert!(doc.contains("app.js"));
79 }
80
81 #[test]
82 fn attribute_and_text_mixed_pipeline() {
83 let html = r#"<div><a href="%%SEAM:link.url%%">%%SEAM:link.text%%</a></div>"#;
84 let result = sentinel_to_slots(html);
85 let doc = wrap_document(&result, &[], &[], false, None, "__seam");
86 assert!(doc.contains("<!--seam:link.url:attr:href-->"));
87 assert!(doc.contains("<!--seam:link.text-->"));
88 assert!(!doc.contains("%%SEAM:"));
89 }
90
91 #[test]
92 fn float_metadata_stays_in_body_with_wrap_document() {
93 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>"#;
96
97 let slotted = sentinel_to_slots(html);
98 assert!(slotted.contains("<!--seam:t-->"));
99 assert!(slotted.contains("<!--seam:d:attr:content-->"));
100 assert!(slotted.contains("<!--seam:u:attr:href-->"));
101
102 let doc =
103 wrap_document(&slotted, &["style.css".into()], &["app.js".into()], false, None, "__seam");
104 assert!(doc.starts_with("<!DOCTYPE html>"));
105
106 let root = &doc[doc.find("__seam").unwrap()..];
108 assert!(root.contains("<!--seam:t-->"), "title slot in body");
109 assert!(root.contains("<!--seam:d:attr:content-->"), "meta slot in body");
110 assert!(root.contains("<!--seam:body-->"), "body content in body");
111 }
112
113 #[test]
114 fn float_metadata_with_boolean_axis() {
115 let with_meta = sentinel_to_slots(
117 r#"<title>%%SEAM:t%%</title><meta name="og" content="%%SEAM:og%%"><p>%%SEAM:body%%</p>"#,
118 );
119 let without_meta = sentinel_to_slots(r#"<title>%%SEAM:t%%</title><p>%%SEAM:body%%</p>"#);
120
121 let axes = vec![make_axis("og", "nullable", vec![json!("present"), json!(null)])];
122 let variants = vec![with_meta, without_meta];
123 let template = extract_template(&axes, &variants);
124
125 assert!(template.contains("<!--seam:if:og-->"));
126 assert!(template.contains("<!--seam:endif:og-->"));
127 assert!(template.contains("<!--seam:t-->"));
128 assert!(template.contains("<!--seam:body-->"));
129
130 let doc = wrap_document(&template, &[], &[], false, None, "__seam");
131 let root = &doc[doc.find("__seam").unwrap()..];
133 assert!(root.contains("<!--seam:t-->"), "title slot in body");
134 assert!(root.contains("<!--seam:if:og-->"), "conditional in body");
135 }
136}