hwpforge_blueprint/
builtins.rs1use crate::error::BlueprintResult;
10use crate::template::Template;
11
12pub const DEFAULT_YAML: &str = include_str!("../templates/default.yaml");
14
15pub const GOV_PROPOSAL_YAML: &str = include_str!("../templates/gov_proposal.yaml");
17
18pub fn builtin_default() -> BlueprintResult<Template> {
30 Template::from_yaml(DEFAULT_YAML)
31}
32
33pub fn builtin_gov_proposal() -> BlueprintResult<Template> {
48 Template::from_yaml(GOV_PROPOSAL_YAML)
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use crate::inheritance::resolve_template;
55 use crate::registry::StyleRegistry;
56 use hwpforge_foundation::{Alignment, HwpUnit};
57 use pretty_assertions::assert_eq;
58 use std::collections::HashMap;
59
60 #[test]
65 fn default_yaml_is_valid() {
66 let tmpl = builtin_default().unwrap();
67 assert_eq!(tmpl.meta.name, "default");
68 assert_eq!(tmpl.meta.version, "1.0.0");
69 assert!(tmpl.meta.extends.is_none());
70 }
71
72 #[test]
73 fn gov_proposal_yaml_is_valid() {
74 let tmpl = builtin_gov_proposal().unwrap();
75 assert_eq!(tmpl.meta.name, "gov_proposal");
76 assert_eq!(tmpl.meta.extends, Some("default".to_string()));
77 }
78
79 #[test]
84 fn default_has_expected_styles() {
85 let tmpl = builtin_default().unwrap();
86 let expected =
87 ["body", "heading1", "heading2", "heading3", "code", "blockquote", "list_item"];
88 for name in &expected {
89 assert!(tmpl.styles.contains_key(*name), "missing style: {name}");
90 }
91 assert_eq!(tmpl.styles.len(), expected.len());
92 }
93
94 #[test]
95 fn default_has_a4_page() {
96 let tmpl = builtin_default().unwrap();
97 let page = tmpl.page.as_ref().unwrap();
98 let settings = page.to_page_settings();
99 assert!((settings.width.to_mm() - 210.0).abs() < 0.5);
100 assert!((settings.height.to_mm() - 297.0).abs() < 0.5);
101 }
102
103 #[test]
104 fn default_body_is_10pt_justify() {
105 let tmpl = builtin_default().unwrap();
106 let body = tmpl.styles.get("body").unwrap();
107 let cs = body.char_shape.as_ref().unwrap();
108 assert_eq!(cs.font, Some("한컴바탕".to_string()));
109 assert_eq!(cs.size, Some(HwpUnit::from_pt(10.0).unwrap()));
110 let ps = body.para_shape.as_ref().unwrap();
111 assert_eq!(ps.alignment, Some(Alignment::Justify));
112 }
113
114 #[test]
115 fn default_heading1_is_bold_16pt() {
116 let tmpl = builtin_default().unwrap();
117 let h1 = tmpl.styles.get("heading1").unwrap();
118 let cs = h1.char_shape.as_ref().unwrap();
119 assert_eq!(cs.size, Some(HwpUnit::from_pt(16.0).unwrap()));
120 assert_eq!(cs.bold, Some(true));
121 }
122
123 #[test]
124 fn default_has_markdown_mapping() {
125 let tmpl = builtin_default().unwrap();
126 let md = tmpl.markdown_mapping.as_ref().unwrap();
127 assert_eq!(md.body, Some("body".to_string()));
128 assert_eq!(md.heading1, Some("heading1".to_string()));
129 assert_eq!(md.code, Some("code".to_string()));
130 }
131
132 #[test]
137 fn gov_proposal_has_title_style() {
138 let tmpl = builtin_gov_proposal().unwrap();
139 assert!(tmpl.styles.contains_key("title"));
140 assert!(tmpl.styles.contains_key("subtitle"));
141 }
142
143 #[test]
144 fn gov_proposal_overrides_body_size() {
145 let tmpl = builtin_gov_proposal().unwrap();
146 let body = tmpl.styles.get("body").unwrap();
147 let cs = body.char_shape.as_ref().unwrap();
148 assert_eq!(cs.size, Some(HwpUnit::from_pt(11.0).unwrap()));
149 }
150
151 #[test]
152 fn gov_proposal_wider_left_margin() {
153 let tmpl = builtin_gov_proposal().unwrap();
154 let page = tmpl.page.as_ref().unwrap();
155 assert_eq!(page.margin_left, Some(HwpUnit::from_mm(30.0).unwrap()));
156 }
157
158 #[test]
163 fn gov_proposal_resolves_from_default() {
164 let default = builtin_default().unwrap();
165 let gov = builtin_gov_proposal().unwrap();
166
167 let mut provider = HashMap::new();
168 provider.insert("default".to_string(), default);
169 provider.insert("gov_proposal".to_string(), gov.clone());
170
171 let resolved = resolve_template(&gov, &provider).unwrap();
172
173 assert!(resolved.meta.extends.is_none());
175 assert!(resolved.styles.contains_key("body")); assert!(resolved.styles.contains_key("heading1")); assert!(resolved.styles.contains_key("title")); assert!(resolved.styles.contains_key("subtitle")); assert!(resolved.styles.contains_key("code")); let body = resolved.styles.get("body").unwrap();
183 let cs = body.char_shape.as_ref().unwrap();
184 assert_eq!(cs.font, Some("한컴바탕".to_string()));
186 assert_eq!(cs.size, Some(HwpUnit::from_pt(11.0).unwrap()));
188 }
189
190 #[test]
195 fn default_template_creates_valid_registry() {
196 let tmpl = builtin_default().unwrap();
197 let registry = StyleRegistry::from_template(&tmpl).unwrap();
198
199 assert_eq!(registry.style_count(), 7);
200 assert!(registry.get_style("body").is_some());
201 assert!(registry.get_style("heading1").is_some());
202 }
203
204 #[test]
205 fn resolved_gov_proposal_creates_valid_registry() {
206 let default = builtin_default().unwrap();
207 let gov = builtin_gov_proposal().unwrap();
208
209 let mut provider = HashMap::new();
210 provider.insert("default".to_string(), default);
211 provider.insert("gov_proposal".to_string(), gov.clone());
212
213 let resolved = resolve_template(&gov, &provider).unwrap();
214 let registry = StyleRegistry::from_template(&resolved).unwrap();
215
216 assert_eq!(registry.style_count(), 9);
218 assert!(registry.get_style("title").is_some());
219
220 assert_eq!(registry.font_count(), 2);
222 }
223
224 #[test]
229 fn default_yaml_roundtrip() {
230 let original = builtin_default().unwrap();
231 let yaml = serde_yaml::to_string(&original).unwrap();
232 let roundtripped = Template::from_yaml(&yaml).unwrap();
233 assert_eq!(original.meta.name, roundtripped.meta.name);
234 assert_eq!(original.styles.len(), roundtripped.styles.len());
235 }
236
237 #[test]
238 fn gov_proposal_yaml_roundtrip() {
239 let original = builtin_gov_proposal().unwrap();
240 let yaml = serde_yaml::to_string(&original).unwrap();
241 let roundtripped = Template::from_yaml(&yaml).unwrap();
242 assert_eq!(original.meta.name, roundtripped.meta.name);
243 assert_eq!(original.styles.len(), roundtripped.styles.len());
244 }
245}