1mod defs;
24mod error;
25mod escape;
26mod metrics;
27mod render;
28mod resolve;
29
30pub use error::SvgError;
31pub use render::render_svg;
32
33pub fn version() -> &'static str {
35 env!("CARGO_PKG_VERSION")
36}
37
38#[cfg(test)]
39mod tests {
40 use dendryform_core::{
41 Color, Connector, ConnectorStyle, Container, ContainerBorder, Diagram, DiagramHeader,
42 FlowLabels, Layer, LegendEntry, Node, NodeId, NodeKind, RawDiagram, Theme, Tier, Title,
43 };
44 use dendryform_layout::compute_layout;
45
46 use super::*;
47
48 fn test_node(id: &str, color: Color) -> Node {
49 Node::builder()
50 .id(NodeId::new(id).unwrap())
51 .kind(NodeKind::System)
52 .color(color)
53 .icon("\u{25c7}")
54 .title(id)
55 .description("test node")
56 .build()
57 .unwrap()
58 }
59
60 fn make_diagram(layers: Vec<Layer>, legend: Vec<LegendEntry>) -> Diagram {
61 let raw = RawDiagram {
62 diagram: DiagramHeader::new(Title::new("test", "accent"), "subtitle", "dark"),
63 layers,
64 legend,
65 edges: vec![],
66 };
67 Diagram::try_from(raw).unwrap()
68 }
69
70 #[test]
71 fn test_version_is_set() {
72 assert_eq!(version(), "0.1.0");
73 }
74
75 #[test]
76 fn test_render_minimal() {
77 let diagram = make_diagram(
78 vec![Layer::Tier(Tier::new(
79 NodeId::new("main").unwrap(),
80 vec![test_node("app", Color::Blue)],
81 ))],
82 vec![],
83 );
84 let plan = compute_layout(&diagram).unwrap();
85 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
86
87 assert!(svg.contains("<svg"));
88 assert!(svg.contains("</svg>"));
89 assert!(svg.contains("accent"));
90 assert!(svg.contains("test"));
91 }
92
93 #[test]
94 fn test_render_contains_defs() {
95 let diagram = make_diagram(
96 vec![Layer::Tier(Tier::new(
97 NodeId::new("main").unwrap(),
98 vec![test_node("app", Color::Blue)],
99 ))],
100 vec![],
101 );
102 let plan = compute_layout(&diagram).unwrap();
103 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
104
105 assert!(svg.contains("<defs>"));
106 assert!(svg.contains("arrowhead"));
107 assert!(svg.contains("connector-grad"));
108 assert!(svg.contains("@import url"));
109 }
110
111 #[test]
112 fn test_render_contains_node() {
113 let diagram = make_diagram(
114 vec![Layer::Tier(Tier::new(
115 NodeId::new("main").unwrap(),
116 vec![test_node("myapp", Color::Green)],
117 ))],
118 vec![],
119 );
120 let plan = compute_layout(&diagram).unwrap();
121 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
122
123 assert!(svg.contains("myapp"));
124 assert!(svg.contains("#3ddc84"));
126 }
127
128 #[test]
129 fn test_render_contains_connector() {
130 let diagram = make_diagram(
131 vec![
132 Layer::Tier(Tier::new(
133 NodeId::new("top").unwrap(),
134 vec![test_node("a", Color::Blue)],
135 )),
136 Layer::Connector(Connector::with_label(ConnectorStyle::Line, "HTTPS")),
137 Layer::Tier(Tier::new(
138 NodeId::new("bottom").unwrap(),
139 vec![test_node("b", Color::Green)],
140 )),
141 ],
142 vec![],
143 );
144 let plan = compute_layout(&diagram).unwrap();
145 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
146
147 assert!(svg.contains("HTTPS"));
148 assert!(svg.contains("marker-end"));
149 }
150
151 #[test]
152 fn test_render_contains_container() {
153 let container = Container::new(
154 "server",
155 ContainerBorder::Solid,
156 Color::Green,
157 vec![Layer::Tier(Tier::new(
158 NodeId::new("inner").unwrap(),
159 vec![test_node("api", Color::Green)],
160 ))],
161 );
162 let diagram = make_diagram(
163 vec![Layer::Tier(Tier::with_container(
164 NodeId::new("server").unwrap(),
165 container,
166 ))],
167 vec![],
168 );
169 let plan = compute_layout(&diagram).unwrap();
170 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
171
172 assert!(svg.contains("SERVER"));
173 assert!(svg.contains("api"));
174 }
175
176 #[test]
177 fn test_render_contains_legend() {
178 let diagram = make_diagram(
179 vec![Layer::Tier(Tier::new(
180 NodeId::new("main").unwrap(),
181 vec![test_node("a", Color::Blue)],
182 ))],
183 vec![
184 LegendEntry::new(Color::Blue, "Clients"),
185 LegendEntry::new(Color::Green, "Servers"),
186 ],
187 );
188 let plan = compute_layout(&diagram).unwrap();
189 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
190
191 assert!(svg.contains("Clients"));
192 assert!(svg.contains("Servers"));
193 assert!(svg.contains("width=\"10\""));
195 }
196
197 #[test]
198 fn test_render_contains_flow_labels() {
199 let diagram = make_diagram(
200 vec![
201 Layer::Tier(Tier::new(
202 NodeId::new("top").unwrap(),
203 vec![test_node("a", Color::Blue)],
204 )),
205 Layer::FlowLabels(FlowLabels::new(vec![
206 "SQL queries".to_owned(),
207 "cache reads".to_owned(),
208 ])),
209 Layer::Tier(Tier::new(
210 NodeId::new("bottom").unwrap(),
211 vec![test_node("b", Color::Red)],
212 )),
213 ],
214 vec![],
215 );
216 let plan = compute_layout(&diagram).unwrap();
217 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
218
219 assert!(svg.contains("SQL queries"));
220 assert!(svg.contains("\u{2193}")); }
222
223 #[test]
224 fn test_render_internal_connector() {
225 let container = Container::new(
226 "server",
227 ContainerBorder::Solid,
228 Color::Green,
229 vec![
230 Layer::Tier(Tier::new(
231 NodeId::new("inner1").unwrap(),
232 vec![test_node("a", Color::Green)],
233 )),
234 Layer::Connector(Connector::new(ConnectorStyle::Dots)),
235 Layer::Tier(Tier::new(
236 NodeId::new("inner2").unwrap(),
237 vec![test_node("b", Color::Green)],
238 )),
239 ],
240 );
241 let diagram = make_diagram(
242 vec![Layer::Tier(Tier::with_container(
243 NodeId::new("server").unwrap(),
244 container,
245 ))],
246 vec![],
247 );
248 let plan = compute_layout(&diagram).unwrap();
249 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
250
251 assert!(svg.contains("<circle"));
253 }
254
255 #[test]
256 fn test_xml_escaping() {
257 let node = Node::builder()
258 .id(NodeId::new("test").unwrap())
259 .kind(NodeKind::System)
260 .color(Color::Blue)
261 .icon("<>")
262 .title("A & B")
263 .description("x < y > z")
264 .build()
265 .unwrap();
266 let diagram = make_diagram(
267 vec![Layer::Tier(Tier::new(
268 NodeId::new("main").unwrap(),
269 vec![node],
270 ))],
271 vec![],
272 );
273 let plan = compute_layout(&diagram).unwrap();
274 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
275
276 assert!(svg.contains("&"));
277 assert!(svg.contains("<"));
278 assert!(svg.contains(">"));
279 assert!(!svg.contains("A & B"));
280 }
281
282 #[test]
283 fn test_render_background_rect() {
284 let diagram = make_diagram(
285 vec![Layer::Tier(Tier::new(
286 NodeId::new("main").unwrap(),
287 vec![test_node("a", Color::Blue)],
288 ))],
289 vec![],
290 );
291 let plan = compute_layout(&diagram).unwrap();
292 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
293
294 assert!(svg.contains("#0a0e14")); assert!(svg.contains("viewBox=\"0 0 1100"));
296 }
297
298 #[test]
299 fn test_render_taproot_svg() {
300 let yaml = include_str!("../../../examples/taproot/architecture.yaml");
301 let diagram = dendryform_parse::parse_yaml(yaml).unwrap();
302 let plan = compute_layout(&diagram).unwrap();
303 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
304
305 assert!(svg.contains("<svg"));
306 assert!(svg.contains("taproot"));
307 assert!(svg.contains("system architecture"));
308 assert!(svg.contains("Claude Desktop"));
309 assert!(svg.contains("Streamable HTTP + SSE"));
310 assert!(svg.contains("BigQuery"));
311 assert!(svg.contains("SQL queries"));
312 assert!(svg.contains("Client / Auth"));
313 }
314
315 #[test]
316 fn test_render_ai_kasu_svg() {
317 let yaml = include_str!("../../../examples/ai-kasu/architecture.yaml");
318 let diagram = dendryform_parse::parse_yaml(yaml).unwrap();
319 let plan = compute_layout(&diagram).unwrap();
320 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
321
322 assert!(svg.contains("<svg"));
323 assert!(svg.contains("ai-kasu"));
324 assert!(svg.contains("MCP server architecture"));
325 assert!(svg.contains("Claude Code"));
326 assert!(svg.contains("KnowledgeEngine"));
327 assert!(svg.contains("SERVER"));
328 }
329
330 #[test]
331 fn test_render_oxur_lisp_svg() {
332 let yaml = include_str!("../../../examples/oxur-lisp/architecture.yaml");
333 let diagram = dendryform_parse::parse_yaml(yaml).unwrap();
334 let plan = compute_layout(&diagram).unwrap();
335 let svg = render_svg(&plan, &Theme::dark(), 1100.0).unwrap();
336
337 assert!(svg.contains("<svg"));
338 assert!(svg.contains("oxur"));
339 assert!(svg.contains("language architecture"));
340 assert!(svg.contains("OXUR COMPILATION PIPELINE"));
341 assert!(svg.contains("Frontend (oxur-lang)"));
342 }
343}