1pub(crate) mod builtins;
4pub mod engine;
5pub mod plot;
6
7#[cfg(test)]
8mod tests {
9 use crate::engine::*;
10 use dashmap::DashMap;
11 use std::sync::Arc;
12
13 #[test]
14 fn test_from_str_single_letter_columns() {
15 let a1 = CellRef::from_str("A1").unwrap();
16 assert_eq!(a1.row, 0);
17 assert_eq!(a1.col, 0);
18
19 let b1 = CellRef::from_str("B1").unwrap();
20 assert_eq!(b1.row, 0);
21 assert_eq!(b1.col, 1);
22
23 let z1 = CellRef::from_str("Z1").unwrap();
24 assert_eq!(z1.row, 0);
25 assert_eq!(z1.col, 25);
26 }
27
28 #[test]
29 fn test_from_str_multi_letter_columns() {
30 let aa1 = CellRef::from_str("AA1").unwrap();
31 assert_eq!(aa1.col, 26);
32
33 let ab1 = CellRef::from_str("AB1").unwrap();
34 assert_eq!(ab1.col, 27);
35
36 let az1 = CellRef::from_str("AZ1").unwrap();
37 assert_eq!(az1.col, 51);
38
39 let ba1 = CellRef::from_str("BA1").unwrap();
40 assert_eq!(ba1.col, 52);
41 }
42
43 #[test]
44 fn test_from_str_row_numbers() {
45 let a1 = CellRef::from_str("A1").unwrap();
46 assert_eq!(a1.row, 0);
47
48 let a10 = CellRef::from_str("A10").unwrap();
49 assert_eq!(a10.row, 9);
50
51 let a100 = CellRef::from_str("A100").unwrap();
52 assert_eq!(a100.row, 99);
53 }
54
55 #[test]
56 fn test_from_str_case_insensitive() {
57 let lower = CellRef::from_str("a1").unwrap();
58 assert_eq!(lower.row, 0);
59 assert_eq!(lower.col, 0);
60
61 let mixed = CellRef::from_str("aA1").unwrap();
62 assert_eq!(mixed.col, 26);
63 }
64
65 #[test]
66 fn test_from_str_invalid_inputs() {
67 assert!(CellRef::from_str("").is_none());
68 assert!(CellRef::from_str("123").is_none());
69 assert!(CellRef::from_str("ABC").is_none());
70 assert!(CellRef::from_str("A0").is_none());
71 assert!(CellRef::from_str("1A").is_none());
72 assert!(CellRef::from_str("A 1").is_none());
73 }
74
75 #[test]
76 fn test_preprocess_script_simple() {
77 assert_eq!(preprocess_script("A1"), "cell(0, 0)");
78 assert_eq!(preprocess_script("B1"), "cell(0, 1)");
79 assert_eq!(preprocess_script("A2"), "cell(1, 0)");
80 }
81
82 #[test]
83 fn test_preprocess_script_typed_refs() {
84 assert_eq!(preprocess_script("@A1"), "value(0, 0)");
85 assert_eq!(preprocess_script("len(@B1)"), "len(value(0, 1))");
86 assert_eq!(preprocess_script("@A1 + B1"), "value(0, 0) + cell(0, 1)");
87 }
88
89 #[test]
90 fn test_preprocess_script_expression() {
91 assert_eq!(preprocess_script("A1 + B1"), "cell(0, 0) + cell(0, 1)");
92 assert_eq!(
93 preprocess_script("A1 * B2 + C3"),
94 "cell(0, 0) * cell(1, 1) + cell(2, 2)"
95 );
96 }
97
98 #[test]
99 fn test_preprocess_script_preserves_other_content() {
100 assert_eq!(preprocess_script("A1 + 10"), "cell(0, 0) + 10");
101 assert_eq!(preprocess_script("print(A1)"), "print(cell(0, 0))");
102 }
103
104 #[test]
105 fn test_extract_dependencies_empty() {
106 assert!(extract_dependencies("").is_empty());
107 assert!(extract_dependencies("10 + 20").is_empty());
108 }
109
110 #[test]
111 fn test_extract_dependencies_single() {
112 let deps = extract_dependencies("A1");
113 assert_eq!(deps.len(), 1);
114 assert_eq!(deps[0], CellRef::new(0, 0));
115 }
116
117 #[test]
118 fn test_extract_dependencies_multiple() {
119 let deps = extract_dependencies("A1 + B1 + C2");
120 assert_eq!(deps.len(), 3);
121 assert_eq!(deps[0], CellRef::new(0, 0));
122 assert_eq!(deps[1], CellRef::new(0, 1));
123 assert_eq!(deps[2], CellRef::new(1, 2));
124 }
125
126 #[test]
127 fn test_extract_dependencies_duplicates() {
128 let deps = extract_dependencies("A1 + A1");
129 assert_eq!(deps.len(), 2);
130 }
131
132 #[test]
133 fn test_detect_cycle_no_cycle() {
134 let grid: Grid = DashMap::new();
135 grid.insert(CellRef::new(0, 0), Cell::new_number(10.0));
136 grid.insert(CellRef::new(0, 1), Cell::new_number(20.0));
137 grid.insert(CellRef::new(0, 2), Cell::new_script("A1 + B1"));
138
139 assert!(detect_cycle(&CellRef::new(0, 2), &grid).is_none());
140 }
141
142 #[test]
143 fn test_detect_cycle_direct() {
144 let grid: Grid = DashMap::new();
145 grid.insert(CellRef::new(0, 0), Cell::new_script("B1"));
146 grid.insert(CellRef::new(0, 1), Cell::new_script("A1"));
147
148 assert!(detect_cycle(&CellRef::new(0, 0), &grid).is_some());
149 assert!(detect_cycle(&CellRef::new(0, 1), &grid).is_some());
150 }
151
152 #[test]
153 fn test_detect_cycle_indirect() {
154 let grid: Grid = DashMap::new();
155 grid.insert(CellRef::new(0, 0), Cell::new_script("B1"));
156 grid.insert(CellRef::new(0, 1), Cell::new_script("C1"));
157 grid.insert(CellRef::new(0, 2), Cell::new_script("A1"));
158
159 let cycle = detect_cycle(&CellRef::new(0, 0), &grid);
160 assert!(cycle.is_some());
161 let path = cycle.unwrap();
162 assert!(path.len() >= 3);
163 }
164
165 #[test]
166 fn test_detect_cycle_self_reference() {
167 let grid: Grid = DashMap::new();
168 grid.insert(CellRef::new(0, 0), Cell::new_script("A1"));
169
170 assert!(detect_cycle(&CellRef::new(0, 0), &grid).is_some());
171 }
172
173 #[test]
174 fn test_parse_range() {
175 let result = parse_range("A1:B5");
176 assert_eq!(result, Some((0, 0, 4, 1)));
177
178 let result = parse_range("B2:D10");
179 assert_eq!(result, Some((1, 1, 9, 3)));
180
181 let result = parse_range("A1");
182 assert_eq!(result, None);
183
184 let result = parse_range("invalid");
185 assert_eq!(result, None);
186 }
187
188 #[test]
189 fn test_preprocess_script_range_functions() {
190 assert_eq!(preprocess_script("SUM(A1:B5)"), "sum_range(0, 0, 4, 1)");
191 assert_eq!(preprocess_script("AVG(A1:A10)"), "avg_range(0, 0, 9, 0)");
192 assert_eq!(preprocess_script("COUNT(B2:D5)"), "count_range(1, 1, 4, 3)");
193 assert_eq!(preprocess_script("MIN(A1:C3)"), "min_range(0, 0, 2, 2)");
194 assert_eq!(preprocess_script("MAX(A1:Z100)"), "max_range(0, 0, 99, 25)");
195 assert_eq!(
196 preprocess_script("BARCHART(A1:A10)"),
197 "barchart_range(0, 0, 9, 0)"
198 );
199 assert_eq!(
200 preprocess_script("LINECHART(A1:A10)"),
201 "linechart_range(0, 0, 9, 0)"
202 );
203 assert_eq!(
204 preprocess_script("SCATTER(A1:B10)"),
205 "scatter_range(0, 0, 9, 1)"
206 );
207 assert_eq!(
208 preprocess_script("SCATTER(A1:B10, \"My Plot\", \"X\", \"Y\")"),
209 "scatter_range(0, 0, 9, 1, \"My Plot\", \"X\", \"Y\")"
210 );
211 assert_eq!(
212 preprocess_script("SCATTER(A1:B10, \"A1\", \"B2\", \"C3\")"),
213 "scatter_range(0, 0, 9, 1, \"A1\", \"B2\", \"C3\")"
214 );
215 }
216
217 #[test]
218 fn test_preprocess_script_mixed() {
219 assert_eq!(
220 preprocess_script("SUM(A1:A3) + B1"),
221 "sum_range(0, 0, 2, 0) + cell(0, 1)"
222 );
223 assert_eq!(
224 preprocess_script("SUM(A1:A3) * 2 + AVG(B1:B5)"),
225 "sum_range(0, 0, 2, 0) * 2 + avg_range(0, 1, 4, 1)"
226 );
227 }
228
229 #[test]
230 fn test_range_functions_evaluation() {
231 let grid: Grid = DashMap::new();
232 grid.insert(CellRef::new(0, 0), Cell::new_number(10.0));
233 grid.insert(CellRef::new(1, 0), Cell::new_number(20.0));
234 grid.insert(CellRef::new(2, 0), Cell::new_number(30.0));
235
236 let engine = create_engine(Arc::new(grid));
237
238 let result: f64 = engine.eval("sum_range(0, 0, 2, 0)").unwrap();
239 assert_eq!(result, 60.0);
240
241 let result: f64 = engine.eval("avg_range(0, 0, 2, 0)").unwrap();
242 assert_eq!(result, 20.0);
243
244 let result: f64 = engine.eval("min_range(0, 0, 2, 0)").unwrap();
245 assert_eq!(result, 10.0);
246
247 let result: f64 = engine.eval("max_range(0, 0, 2, 0)").unwrap();
248 assert_eq!(result, 30.0);
249
250 let result: f64 = engine.eval("count_range(0, 0, 2, 0)").unwrap();
251 assert_eq!(result, 3.0);
252 }
253
254 #[test]
255 fn test_typed_ref_len_over_script_string() {
256 let grid: Grid = DashMap::new();
257 grid.insert(CellRef::new(0, 2), Cell::new_number(150.0)); grid.insert(
259 CellRef::new(0, 1),
260 Cell::new_script("if C1 > 100 { \"expensive\" } else { \"cheap\" }"),
261 ); let engine = create_engine(Arc::new(grid));
264 let processed = preprocess_script("len(@B1)");
265 let result = eval_with_functions(&engine, &processed, None).unwrap();
266 assert_eq!(result.as_int().unwrap(), 9);
267 }
268
269 #[test]
270 fn test_extract_dependencies_with_ranges() {
271 let deps = extract_dependencies("SUM(A1:A3)");
272 assert_eq!(deps.len(), 3);
273 assert!(deps.contains(&CellRef::new(0, 0)));
274 assert!(deps.contains(&CellRef::new(1, 0)));
275 assert!(deps.contains(&CellRef::new(2, 0)));
276 }
277
278 #[test]
279 fn test_custom_functions() {
280 let grid: Grid = DashMap::new();
281 let custom_script = r#"
282 fn double(x) { x * 2.0 }
283 fn square(x) { x * x }
284 "#;
285
286 let (engine, custom_ast, error) =
287 create_engine_with_functions(Arc::new(grid), Some(custom_script));
288 assert!(error.is_none());
289 assert!(custom_ast.is_some());
290
291 let result = eval_with_functions(&engine, "double(5.0)", custom_ast.as_ref()).unwrap();
292 assert_eq!(result.as_float().unwrap(), 10.0);
293
294 let result = eval_with_functions(&engine, "square(4.0)", custom_ast.as_ref()).unwrap();
295 assert_eq!(result.as_float().unwrap(), 16.0);
296 }
297
298 #[test]
299 fn test_custom_functions_with_syntax_error() {
300 let grid: Grid = DashMap::new();
301 let bad_script = "fn broken( { }";
302
303 let (_engine, _ast, error) = create_engine_with_functions(Arc::new(grid), Some(bad_script));
304 assert!(error.is_some());
305 assert!(error.unwrap().contains("Error"));
306 }
307}