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