1pub(crate) mod builtins;
4pub mod engine;
5pub mod plot;
6
7#[cfg(test)]
8mod tests {
9 use crate::engine::{*, preprocess_script_with_context};
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_preprocess_row_col_functions() {
106 let cell = CellRef::new(5, 3); assert_eq!(
110 preprocess_script_with_context("ROW()", Some(&cell)),
111 "6"
112 );
113 assert_eq!(
114 preprocess_script_with_context("COL()", Some(&cell)),
115 "4"
116 );
117 assert_eq!(
118 preprocess_script_with_context("ROW() + COL()", Some(&cell)),
119 "6 + 4"
120 );
121 assert_eq!(
123 preprocess_script_with_context("A1 + ROW()", Some(&cell)),
124 "cell(0, 0) + 6"
125 );
126 }
127
128 #[test]
129 fn test_preprocess_row_col_without_context() {
130 assert_eq!(
132 preprocess_script_with_context("ROW()", None),
133 "ROW()"
134 );
135 assert_eq!(
136 preprocess_script_with_context("COL()", None),
137 "COL()"
138 );
139 }
140
141 #[test]
142 fn test_extract_dependencies_empty() {
143 assert!(extract_dependencies("").is_empty());
144 assert!(extract_dependencies("10 + 20").is_empty());
145 }
146
147 #[test]
148 fn test_extract_dependencies_single() {
149 let deps = extract_dependencies("A1");
150 assert_eq!(deps.len(), 1);
151 assert_eq!(deps[0], CellRef::new(0, 0));
152 }
153
154 #[test]
155 fn test_extract_dependencies_multiple() {
156 let deps = extract_dependencies("A1 + B1 + C2");
157 assert_eq!(deps.len(), 3);
158 assert_eq!(deps[0], CellRef::new(0, 0));
159 assert_eq!(deps[1], CellRef::new(0, 1));
160 assert_eq!(deps[2], CellRef::new(1, 2));
161 }
162
163 #[test]
164 fn test_extract_dependencies_duplicates() {
165 let deps = extract_dependencies("A1 + A1");
166 assert_eq!(deps.len(), 2);
167 }
168
169 #[test]
170 fn test_detect_cycle_no_cycle() {
171 let grid: Grid = DashMap::new();
172 grid.insert(CellRef::new(0, 0), Cell::new_number(10.0));
173 grid.insert(CellRef::new(0, 1), Cell::new_number(20.0));
174 grid.insert(CellRef::new(0, 2), Cell::new_script("A1 + B1"));
175
176 assert!(detect_cycle(&CellRef::new(0, 2), &grid).is_none());
177 }
178
179 #[test]
180 fn test_detect_cycle_direct() {
181 let grid: Grid = DashMap::new();
182 grid.insert(CellRef::new(0, 0), Cell::new_script("B1"));
183 grid.insert(CellRef::new(0, 1), Cell::new_script("A1"));
184
185 assert!(detect_cycle(&CellRef::new(0, 0), &grid).is_some());
186 assert!(detect_cycle(&CellRef::new(0, 1), &grid).is_some());
187 }
188
189 #[test]
190 fn test_detect_cycle_indirect() {
191 let grid: Grid = DashMap::new();
192 grid.insert(CellRef::new(0, 0), Cell::new_script("B1"));
193 grid.insert(CellRef::new(0, 1), Cell::new_script("C1"));
194 grid.insert(CellRef::new(0, 2), Cell::new_script("A1"));
195
196 let cycle = detect_cycle(&CellRef::new(0, 0), &grid);
197 assert!(cycle.is_some());
198 let path = cycle.unwrap();
199 assert!(path.len() >= 3);
200 }
201
202 #[test]
203 fn test_detect_cycle_self_reference() {
204 let grid: Grid = DashMap::new();
205 grid.insert(CellRef::new(0, 0), Cell::new_script("A1"));
206
207 assert!(detect_cycle(&CellRef::new(0, 0), &grid).is_some());
208 }
209
210 #[test]
211 fn test_parse_range() {
212 let result = parse_range("A1:B5");
213 assert_eq!(result, Some((0, 0, 4, 1)));
214
215 let result = parse_range("B2:D10");
216 assert_eq!(result, Some((1, 1, 9, 3)));
217
218 let result = parse_range("A1");
219 assert_eq!(result, None);
220
221 let result = parse_range("invalid");
222 assert_eq!(result, None);
223 }
224
225 #[test]
226 fn test_preprocess_script_range_functions() {
227 assert_eq!(preprocess_script("SUM(A1:B5)"), "sum_range(0, 0, 4, 1)");
228 assert_eq!(preprocess_script("AVG(A1:A10)"), "avg_range(0, 0, 9, 0)");
229 assert_eq!(preprocess_script("COUNT(B2:D5)"), "count_range(1, 1, 4, 3)");
230 assert_eq!(preprocess_script("MIN(A1:C3)"), "min_range(0, 0, 2, 2)");
231 assert_eq!(preprocess_script("MAX(A1:Z100)"), "max_range(0, 0, 99, 25)");
232 assert_eq!(
233 preprocess_script("BARCHART(A1:A10)"),
234 "barchart_range(0, 0, 9, 0)"
235 );
236 assert_eq!(
237 preprocess_script("LINECHART(A1:A10)"),
238 "linechart_range(0, 0, 9, 0)"
239 );
240 assert_eq!(
241 preprocess_script("SCATTER(A1:B10)"),
242 "scatter_range(0, 0, 9, 1)"
243 );
244 assert_eq!(
245 preprocess_script("SCATTER(A1:B10, \"My Plot\", \"X\", \"Y\")"),
246 "scatter_range(0, 0, 9, 1, \"My Plot\", \"X\", \"Y\")"
247 );
248 assert_eq!(
249 preprocess_script("SCATTER(A1:B10, \"A1\", \"B2\", \"C3\")"),
250 "scatter_range(0, 0, 9, 1, \"A1\", \"B2\", \"C3\")"
251 );
252 }
253
254 #[test]
255 fn test_preprocess_script_mixed() {
256 assert_eq!(
257 preprocess_script("SUM(A1:A3) + B1"),
258 "sum_range(0, 0, 2, 0) + cell(0, 1)"
259 );
260 assert_eq!(
261 preprocess_script("SUM(A1:A3) * 2 + AVG(B1:B5)"),
262 "sum_range(0, 0, 2, 0) * 2 + avg_range(0, 1, 4, 1)"
263 );
264 }
265
266 #[test]
267 fn test_range_functions_evaluation() {
268 let grid: Grid = DashMap::new();
269 grid.insert(CellRef::new(0, 0), Cell::new_number(10.0));
270 grid.insert(CellRef::new(1, 0), Cell::new_number(20.0));
271 grid.insert(CellRef::new(2, 0), Cell::new_number(30.0));
272
273 let engine = create_engine(Arc::new(grid));
274
275 let result: f64 = engine.eval("sum_range(0, 0, 2, 0)").unwrap();
276 assert_eq!(result, 60.0);
277
278 let result: f64 = engine.eval("avg_range(0, 0, 2, 0)").unwrap();
279 assert_eq!(result, 20.0);
280
281 let result: f64 = engine.eval("min_range(0, 0, 2, 0)").unwrap();
282 assert_eq!(result, 10.0);
283
284 let result: f64 = engine.eval("max_range(0, 0, 2, 0)").unwrap();
285 assert_eq!(result, 30.0);
286
287 let result: f64 = engine.eval("count_range(0, 0, 2, 0)").unwrap();
288 assert_eq!(result, 3.0);
289 }
290
291 #[test]
292 fn test_typed_ref_len_over_script_string() {
293 let grid: Grid = DashMap::new();
294 grid.insert(CellRef::new(0, 2), Cell::new_number(150.0)); grid.insert(
296 CellRef::new(0, 1),
297 Cell::new_script("if C1 > 100 { \"expensive\" } else { \"cheap\" }"),
298 ); let engine = create_engine(Arc::new(grid));
301 let processed = preprocess_script("len(@B1)");
302 let result = eval_with_functions(&engine, &processed, None).unwrap();
303 assert_eq!(result.as_int().unwrap(), 9);
304 }
305
306 #[test]
307 fn test_extract_dependencies_with_ranges() {
308 let deps = extract_dependencies("SUM(A1:A3)");
309 assert_eq!(deps.len(), 3);
310 assert!(deps.contains(&CellRef::new(0, 0)));
311 assert!(deps.contains(&CellRef::new(1, 0)));
312 assert!(deps.contains(&CellRef::new(2, 0)));
313 }
314
315 #[test]
316 fn test_custom_functions() {
317 let grid: Grid = DashMap::new();
318 let custom_script = r#"
319 fn double(x) { x * 2.0 }
320 fn square(x) { x * x }
321 "#;
322
323 let (engine, custom_ast, error) =
324 create_engine_with_functions(Arc::new(grid), Some(custom_script));
325 assert!(error.is_none());
326 assert!(custom_ast.is_some());
327
328 let result = eval_with_functions(&engine, "double(5.0)", custom_ast.as_ref()).unwrap();
329 assert_eq!(result.as_float().unwrap(), 10.0);
330
331 let result = eval_with_functions(&engine, "square(4.0)", custom_ast.as_ref()).unwrap();
332 assert_eq!(result.as_float().unwrap(), 16.0);
333 }
334
335 #[test]
336 fn test_custom_functions_with_syntax_error() {
337 let grid: Grid = DashMap::new();
338 let bad_script = "fn broken( { }";
339
340 let (_engine, _ast, error) = create_engine_with_functions(Arc::new(grid), Some(bad_script));
341 assert!(error.is_some());
342 assert!(error.unwrap().contains("Error"));
343 }
344}