1use crate::escape::ascii_escape_json;
4use crate::page::{
5 build_seam_data, flatten_for_slots, inject_data_script, inject_head_meta, inject_html_lang,
6 I18nOpts, PageConfig,
7};
8
9pub fn render_page(
21 template: &str,
22 loader_data_json: &str,
23 config_json: &str,
24 i18n_opts_json: Option<&str>,
25) -> String {
26 let loader_data: serde_json::Value =
27 serde_json::from_str(loader_data_json).unwrap_or(serde_json::Value::Null);
28 let config: PageConfig = match serde_json::from_str(config_json) {
29 Ok(c) => c,
30 Err(_) => return template.to_string(),
31 };
32 let i18n_opts: Option<I18nOpts> = i18n_opts_json.and_then(|s| serde_json::from_str(s).ok());
33
34 let flat_data = flatten_for_slots(&loader_data);
36
37 let mut html = seam_injector::inject_no_script(template, &flat_data);
39
40 if let Some(ref meta) = config.head_meta {
42 let injected_meta = seam_injector::inject_no_script(meta, &flat_data);
44 html = inject_head_meta(&html, &injected_meta);
45 }
46
47 if let Some(ref opts) = i18n_opts {
49 html = inject_html_lang(&html, &opts.locale);
50 }
51
52 let seam_data = build_seam_data(&loader_data, &config, i18n_opts.as_ref());
54 let json = serde_json::to_string(&seam_data).unwrap_or_default();
55 let escaped = ascii_escape_json(&json);
56 inject_data_script(&html, &config.data_id, &escaped)
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use serde_json::json;
63
64 fn simple_template() -> String {
65 r#"<html><head><meta charset="utf-8"><title>Test</title></head><body><p><!--seam:title--></p></body></html>"#.to_string()
66 }
67
68 #[test]
69 fn render_basic_page() {
70 let template = simple_template();
71 let data = json!({"title": "Hello"}).to_string();
72 let config = json!({"layout_chain": [], "data_id": "__SEAM_DATA__"}).to_string();
73
74 let result = render_page(&template, &data, &config, None);
75 assert!(result.contains("<p>Hello</p>"));
76 assert!(result.contains(r#"<script id="__SEAM_DATA__""#));
77 assert!(result.contains(r#""title":"Hello""#));
78 }
79
80 #[test]
81 fn render_with_layout() {
82 let template = simple_template();
83 let data = json!({"title": "Page", "nav": "NavData"}).to_string();
84 let config = json!({
85 "layout_chain": [{"id": "root", "loader_keys": ["nav"]}],
86 "data_id": "__SEAM_DATA__"
87 })
88 .to_string();
89
90 let result = render_page(&template, &data, &config, None);
91 assert!(result.contains(r#""_layouts""#), "missing _layouts key");
93 assert!(result.contains(r#""root""#), "missing root layout key");
94 assert!(result.contains(r#""title":"Page""#), "missing page-level title");
96 }
97
98 #[test]
99 fn render_with_i18n() {
100 let template = simple_template();
101 let data = json!({"title": "Hello"}).to_string();
102 let config = json!({"layout_chain": [], "data_id": "__SEAM_DATA__"}).to_string();
103 let i18n = json!({
104 "locale": "zh",
105 "default_locale": "en",
106 "messages": {"hello": "你好"}
107 })
108 .to_string();
109
110 let result = render_page(&template, &data, &config, Some(&i18n));
111 assert!(result.contains(r#"<html lang="zh""#));
112 assert!(result.contains(r#""_i18n""#));
113 }
114
115 #[test]
116 fn render_with_head_meta() {
117 let template = simple_template();
118 let data = json!({"title": "Hello"}).to_string();
119 let config = json!({
120 "layout_chain": [],
121 "data_id": "__SEAM_DATA__",
122 "head_meta": r#"<title><!--seam:title--></title>"#
123 })
124 .to_string();
125
126 let result = render_page(&template, &data, &config, None);
127 assert!(result.contains(r#"<meta charset="utf-8"><title>Hello</title>"#));
129 }
130
131 #[test]
132 fn render_invalid_config_returns_template() {
133 let template = "plain html";
134 let result = render_page(template, "{}", "invalid json", None);
135 assert_eq!(result, "plain html");
136 }
137}