Skip to main content

topo_render/
lib.rs

1//! JSONL v0.3, JSON, and human-readable output rendering.
2
3mod jsonl;
4
5pub use jsonl::JsonlWriter;
6
7#[cfg(test)]
8mod tests {
9    use super::*;
10    use topo_core::{FileRole, Language, ScoredFile, SignalBreakdown};
11
12    fn sample_files() -> Vec<ScoredFile> {
13        vec![
14            ScoredFile {
15                path: "src/auth/middleware.rs".to_string(),
16                score: 0.95,
17                signals: SignalBreakdown {
18                    bm25f: 0.8,
19                    heuristic: 0.7,
20                    ..Default::default()
21                },
22                tokens: 1200,
23                language: Language::Rust,
24                role: FileRole::Implementation,
25            },
26            ScoredFile {
27                path: "src/auth/handler.rs".to_string(),
28                score: 0.72,
29                signals: SignalBreakdown {
30                    bm25f: 0.5,
31                    heuristic: 0.6,
32                    ..Default::default()
33                },
34                tokens: 800,
35                language: Language::Rust,
36                role: FileRole::Implementation,
37            },
38        ]
39    }
40
41    #[test]
42    fn jsonl_output_has_three_lines() {
43        let files = sample_files();
44        let output = JsonlWriter::new("auth middleware", "balanced")
45            .max_bytes(Some(100_000))
46            .min_score(0.01)
47            .render(&files, 358)
48            .unwrap();
49
50        let lines: Vec<&str> = output.trim().lines().collect();
51        assert_eq!(lines.len(), 4); // header + 2 files + footer
52    }
53
54    #[test]
55    fn jsonl_header_contains_version() {
56        let files = sample_files();
57        let output = JsonlWriter::new("test query", "balanced")
58            .render(&files, 100)
59            .unwrap();
60
61        let first_line = output.lines().next().unwrap();
62        let header: serde_json::Value = serde_json::from_str(first_line).unwrap();
63        assert_eq!(header["Version"], "0.3");
64    }
65
66    #[test]
67    fn jsonl_header_contains_query() {
68        let files = sample_files();
69        let output = JsonlWriter::new("auth middleware", "balanced")
70            .render(&files, 100)
71            .unwrap();
72
73        let first_line = output.lines().next().unwrap();
74        let header: serde_json::Value = serde_json::from_str(first_line).unwrap();
75        assert_eq!(header["Query"], "auth middleware");
76    }
77
78    #[test]
79    fn jsonl_file_entries_have_required_fields() {
80        let files = sample_files();
81        let output = JsonlWriter::new("test", "balanced")
82            .render(&files, 100)
83            .unwrap();
84
85        let lines: Vec<&str> = output.trim().lines().collect();
86        let file_entry: serde_json::Value = serde_json::from_str(lines[1]).unwrap();
87
88        assert!(file_entry["Path"].is_string());
89        assert!(file_entry["Score"].is_number());
90        assert!(file_entry["Tokens"].is_number());
91        assert!(file_entry["Language"].is_string());
92        assert!(file_entry["Role"].is_string());
93    }
94
95    #[test]
96    fn jsonl_footer_has_totals() {
97        let files = sample_files();
98        let output = JsonlWriter::new("test", "balanced")
99            .render(&files, 358)
100            .unwrap();
101
102        let last_line = output.trim().lines().last().unwrap();
103        let footer: serde_json::Value = serde_json::from_str(last_line).unwrap();
104
105        assert_eq!(footer["TotalFiles"], 2);
106        assert_eq!(footer["TotalTokens"], 2000); // 1200 + 800
107        assert_eq!(footer["ScannedFiles"], 358);
108    }
109
110    #[test]
111    fn jsonl_empty_files_produces_header_and_footer() {
112        let output = JsonlWriter::new("test", "balanced").render(&[], 0).unwrap();
113
114        let lines: Vec<&str> = output.trim().lines().collect();
115        assert_eq!(lines.len(), 2); // header + footer
116    }
117
118    #[test]
119    fn jsonl_each_line_is_valid_json() {
120        let files = sample_files();
121        let output = JsonlWriter::new("test", "balanced")
122            .render(&files, 100)
123            .unwrap();
124
125        for line in output.trim().lines() {
126            let parsed: Result<serde_json::Value, _> = serde_json::from_str(line);
127            assert!(parsed.is_ok(), "Invalid JSON line: {line}");
128        }
129    }
130
131    #[test]
132    fn jsonl_max_bytes_in_header() {
133        let output = JsonlWriter::new("test", "balanced")
134            .max_bytes(Some(50_000))
135            .render(&[], 0)
136            .unwrap();
137
138        let first_line = output.lines().next().unwrap();
139        let header: serde_json::Value = serde_json::from_str(first_line).unwrap();
140        assert_eq!(header["Budget"]["MaxBytes"], 50_000);
141    }
142
143    #[test]
144    fn jsonl_preset_in_header() {
145        let output = JsonlWriter::new("test", "deep").render(&[], 0).unwrap();
146
147        let first_line = output.lines().next().unwrap();
148        let header: serde_json::Value = serde_json::from_str(first_line).unwrap();
149        assert_eq!(header["Preset"], "deep");
150    }
151}