flow_rs_core/spatial/
tests.rs

1//! Tests for spatial indexing
2
3#[cfg(test)]
4mod tests {
5    use crate::graph::NodeBuilder;
6    use crate::types::{Position, Rect, Viewport};
7    
8    use crate::spatial::SpatialIndex;
9    use crate::spatial::SpatialQuery;
10
11    #[test]
12    fn test_spatial_index_creation() {
13        let index = SpatialIndex::new();
14        assert!(index.is_empty());
15        assert_eq!(index.len(), 0);
16    }
17
18    #[test]
19    fn test_insert_and_query() {
20        let mut index = SpatialIndex::new();
21
22        let node = NodeBuilder::<()>::new("test-node")
23            .position(10.0, 10.0)
24            .size(50.0, 30.0)
25            .build();
26
27        index.insert(&node).unwrap();
28        assert_eq!(index.len(), 1);
29
30        // Query overlapping rectangle
31        let query_bounds = Rect::new(0.0, 0.0, 50.0, 50.0);
32        let results = index.query_rect(&query_bounds);
33        assert_eq!(results.len(), 1);
34        assert_eq!(results[0], node.id.clone());
35    }
36
37    #[test]
38    fn test_remove_node() {
39        let mut index = SpatialIndex::new();
40
41        let node = NodeBuilder::<()>::new("test-node")
42            .position(10.0, 10.0)
43            .size(20.0, 20.0)
44            .build();
45
46        index.insert(&node).unwrap();
47        assert_eq!(index.len(), 1);
48
49        // Remove the node
50        let removed = index.remove(&node.id);
51        assert!(removed);
52        assert_eq!(index.len(), 0);
53
54        // Query should return no results
55        let query_bounds = Rect::new(0.0, 0.0, 50.0, 50.0);
56        let results = index.query_rect(&query_bounds);
57        assert_eq!(results.len(), 0);
58    }
59
60    #[test]
61    fn test_update_node() {
62        let mut index = SpatialIndex::new();
63
64        let mut node = NodeBuilder::<()>::new("test-node")
65            .position(10.0, 10.0)
66            .size(20.0, 20.0)
67            .build();
68
69        index.insert(&node).unwrap();
70
71        // Move node to new position
72        node.set_position(Position::new(100.0, 100.0));
73        index.update(&node).unwrap();
74
75        // Old position should return no results
76        let old_query = Rect::new(0.0, 0.0, 50.0, 50.0);
77        let results = index.query_rect(&old_query);
78        assert_eq!(results.len(), 0);
79
80        // New position should return the node
81        let new_query = Rect::new(90.0, 90.0, 50.0, 50.0);
82        let results = index.query_rect(&new_query);
83        assert_eq!(results.len(), 1);
84    }
85
86    #[test]
87    fn test_bulk_load() {
88        let mut index = SpatialIndex::new();
89
90        let nodes = vec![
91            NodeBuilder::<()>::new("node1")
92                .position(10.0, 10.0)
93                .size(20.0, 20.0)
94                .build(),
95            NodeBuilder::<()>::new("node2")
96                .position(50.0, 50.0)
97                .size(20.0, 20.0)
98                .build(),
99            NodeBuilder::<()>::new("node3")
100                .position(90.0, 90.0)
101                .size(20.0, 20.0)
102                .build(),
103        ];
104
105        index.bulk_load(&nodes).unwrap();
106        assert_eq!(index.len(), 3);
107
108        // Query all nodes
109        let query_bounds = Rect::new(0.0, 0.0, 120.0, 120.0);
110        let results = index.query_rect(&query_bounds);
111        assert_eq!(results.len(), 3);
112    }
113
114    #[test]
115    fn test_spatial_query_builder() {
116        let mut index = SpatialIndex::new();
117
118        let nodes = vec![
119            NodeBuilder::<()>::new("node1")
120                .position(10.0, 10.0)
121                .size(20.0, 20.0)
122                .build(),
123            NodeBuilder::<()>::new("node2")
124                .position(50.0, 50.0)
125                .size(20.0, 20.0)
126                .build(),
127            NodeBuilder::<()>::new("node3")
128                .position(90.0, 90.0)
129                .size(20.0, 20.0)
130                .build(),
131        ];
132
133        index.bulk_load(&nodes).unwrap();
134
135        // Query with bounds and limit
136        let results = SpatialQuery::new(&index)
137            .bounds(Rect::new(0.0, 0.0, 120.0, 120.0))
138            .limit(2)
139            .execute();
140
141        assert_eq!(results.len(), 2);
142
143        // Query with radius
144        let results = SpatialQuery::new(&index)
145            .radius(Position::new(60.0, 60.0), 30.0)
146            .execute();
147
148        assert!(!results.is_empty());
149    }
150
151    #[test]
152    fn test_grid_cells() {
153        let index = SpatialIndex::with_cell_size(50.0);
154
155        // Test single cell
156        let bounds = Rect::new(10.0, 10.0, 20.0, 20.0);
157        let cells = index.get_grid_cells_for_bounds(&bounds);
158        assert_eq!(cells.len(), 1);
159
160        // Test multiple cells
161        let bounds = Rect::new(10.0, 10.0, 100.0, 100.0);
162        let cells = index.get_grid_cells_for_bounds(&bounds);
163        assert_eq!(cells.len(), 9); // 3x3 grid (from cell 0,0 to cell 2,2)
164    }
165
166    #[test]
167    fn test_viewport_query() {
168        let mut index = SpatialIndex::new();
169
170        let node = NodeBuilder::<()>::new("test-node")
171            .position(50.0, 50.0)
172            .size(20.0, 20.0)
173            .build();
174
175        index.insert(&node).unwrap();
176
177        // Create viewport that includes the node
178        let viewport = Viewport::with_size(0.0, 0.0, 100.0, 100.0);
179        let results = index.query_viewport(&viewport);
180        assert_eq!(results.len(), 1);
181
182        // Create viewport that doesn't include the node
183        let viewport = Viewport::with_size(200.0, 200.0, 50.0, 50.0);
184        let results = index.query_viewport(&viewport);
185        assert_eq!(results.len(), 0);
186    }
187
188    #[test]
189    fn test_radius_query() {
190        let mut index = SpatialIndex::new();
191
192        let node = NodeBuilder::<()>::new("test-node")
193            .position(50.0, 50.0)
194            .size(20.0, 20.0)
195            .build();
196
197        index.insert(&node).unwrap();
198
199        // Query with radius that includes the node
200        let results = index.query_radius(Position::new(60.0, 60.0), 20.0);
201        assert_eq!(results.len(), 1);
202
203        // Query with radius that doesn't include the node
204        let results = index.query_radius(Position::new(100.0, 100.0), 10.0);
205        assert_eq!(results.len(), 0);
206    }
207
208    #[test]
209    fn test_nearest_query() {
210        let mut index = SpatialIndex::new();
211
212        let node1 = NodeBuilder::<()>::new("node1")
213            .position(10.0, 10.0)
214            .size(20.0, 20.0)
215            .build();
216
217        let node2 = NodeBuilder::<()>::new("node2")
218            .position(100.0, 100.0)
219            .size(20.0, 20.0)
220            .build();
221
222        index.insert(&node1).unwrap();
223        index.insert(&node2).unwrap();
224
225        // Query nearest to node1
226        let nearest = index.nearest(Position::new(20.0, 20.0));
227        assert_eq!(nearest, Some(node1.id.clone()));
228
229        // Query nearest to node2
230        let nearest = index.nearest(Position::new(110.0, 110.0));
231        assert_eq!(nearest, Some(node2.id.clone()));
232    }
233
234    #[test]
235    fn test_bounds_calculation() {
236        let mut index = SpatialIndex::new();
237
238        // Empty index should have no bounds
239        assert_eq!(index.bounds(), None);
240
241        let node1 = NodeBuilder::<()>::new("node1")
242            .position(10.0, 10.0)
243            .size(20.0, 20.0)
244            .build();
245
246        let node2 = NodeBuilder::<()>::new("node2")
247            .position(50.0, 50.0)
248            .size(30.0, 30.0)
249            .build();
250
251        index.insert(&node1).unwrap();
252        index.insert(&node2).unwrap();
253
254        let bounds = index.bounds().unwrap();
255        assert_eq!(bounds.x, 10.0);
256        assert_eq!(bounds.y, 10.0);
257        assert_eq!(bounds.width, 70.0); // 50 + 30 - 10
258        assert_eq!(bounds.height, 70.0); // 50 + 30 - 10
259    }
260
261    #[test]
262    fn test_custom_cell_size() {
263        let index = SpatialIndex::with_cell_size(25.0);
264        assert_eq!(index.cell_size(), 25.0);
265    }
266
267    #[test]
268    fn test_default_implementation() {
269        let index = SpatialIndex::default();
270        assert!(index.is_empty());
271        assert_eq!(index.len(), 0);
272        assert_eq!(index.cell_size(), 100.0);
273    }
274
275    #[test]
276    fn test_spatial_index_nan_infinity_handling() {
277        let index = SpatialIndex::new();
278
279        // Test bounds with NaN values
280        let nan_bounds = Rect::new(f64::NAN, 10.0, 50.0, 50.0);
281        let cells_nan = index.get_grid_cells_for_bounds(&nan_bounds);
282        assert_eq!(cells_nan.len(), 0);
283
284        // Test bounds with infinity values
285        let inf_bounds = Rect::new(f64::INFINITY, 10.0, 50.0, 50.0);
286        let cells_inf = index.get_grid_cells_for_bounds(&inf_bounds);
287        assert_eq!(cells_inf.len(), 0);
288
289        // Test bounds with negative infinity
290        let neg_inf_bounds = Rect::new(f64::NEG_INFINITY, 10.0, 50.0, 50.0);
291        let cells_neg_inf = index.get_grid_cells_for_bounds(&neg_inf_bounds);
292        assert_eq!(cells_neg_inf.len(), 0);
293
294        // Test query with NaN position
295        let nan_pos = Position::new(f64::NAN, 10.0);
296        let nan_results = index.query_radius(nan_pos, 50.0);
297        assert_eq!(nan_results.len(), 0);
298    }
299
300    #[test]
301    fn test_spatial_index_viewport_query_edge_cases() {
302        let mut index = SpatialIndex::new();
303
304        let node = NodeBuilder::<()>::new("test-node")
305            .position(100.0, 100.0)
306            .size(50.0, 50.0)
307            .build();
308
309        index.insert(&node).unwrap();
310
311        // Test viewport query with various viewport configurations
312        let viewport1 = Viewport::with_size(0.0, 0.0, 200.0, 200.0);
313        let results1 = index.query_viewport(&viewport1);
314        assert_eq!(results1.len(), 1);
315
316        // Test viewport query that doesn't include the node
317        let viewport2 = Viewport::with_size(200.0, 200.0, 100.0, 100.0);
318        let results2 = index.query_viewport(&viewport2);
319        assert_eq!(results2.len(), 0);
320
321        // Test viewport query with very small viewport
322        let viewport3 = Viewport::with_size(125.0, 125.0, 1.0, 1.0);
323        let results3 = index.query_viewport(&viewport3);
324        assert_eq!(results3.len(), 1);
325    }
326
327    #[test]
328    fn test_spatial_query_builder_edge_cases() {
329        let mut index = SpatialIndex::new();
330
331        let nodes = vec![
332            NodeBuilder::<()>::new("node1")
333                .position(10.0, 10.0)
334                .size(20.0, 20.0)
335                .build(),
336            NodeBuilder::<()>::new("node2")
337                .position(50.0, 50.0)
338                .size(20.0, 20.0)
339                .build(),
340            NodeBuilder::<()>::new("node3")
341                .position(90.0, 90.0)
342                .size(20.0, 20.0)
343                .build(),
344        ];
345
346        index.bulk_load(&nodes).unwrap();
347
348        // Test query with no constraints (should return all nodes)
349        let results = SpatialQuery::new(&index).execute();
350        assert_eq!(results.len(), 3);
351
352        // Test query with limit larger than available results
353        let results = SpatialQuery::new(&index).limit(10).execute();
354        assert_eq!(results.len(), 3);
355
356        // Test query with limit of 0
357        let results = SpatialQuery::new(&index).limit(0).execute();
358        assert_eq!(results.len(), 0);
359
360        // Test query with both bounds and radius (bounds should take precedence)
361        let results = SpatialQuery::new(&index)
362            .bounds(Rect::new(0.0, 0.0, 40.0, 40.0))
363            .radius(Position::new(60.0, 60.0), 100.0)
364            .execute();
365        assert_eq!(results.len(), 1); // Only node1 should be in bounds
366    }
367
368    #[test]
369    fn test_spatial_index_zero_negative_size_bounds() {
370        let index = SpatialIndex::new();
371
372        // Test with negative width (should return 0 due to validation)
373        let neg_width_bounds = Rect::new(50.0, 50.0, -20.0, 20.0);
374        let cells_neg_width = index.get_grid_cells_for_bounds(&neg_width_bounds);
375        assert_eq!(cells_neg_width.len(), 0);
376
377        // Test with negative height (should return 0 due to validation)
378        let neg_height_bounds = Rect::new(50.0, 50.0, 20.0, -20.0);
379        let cells_neg_height = index.get_grid_cells_for_bounds(&neg_height_bounds);
380        assert_eq!(cells_neg_height.len(), 0);
381
382        // Test with both negative dimensions (should return 0 due to validation)
383        let neg_both_bounds = Rect::new(50.0, 50.0, -20.0, -20.0);
384        let cells_neg_both = index.get_grid_cells_for_bounds(&neg_both_bounds);
385        assert_eq!(cells_neg_both.len(), 0);
386    }
387
388    #[test]
389    fn test_spatial_index_clear_functionality() {
390        let mut index = SpatialIndex::new();
391
392        let node1 = NodeBuilder::<()>::new("node1")
393            .position(10.0, 10.0)
394            .size(20.0, 20.0)
395            .build();
396
397        let node2 = NodeBuilder::<()>::new("node2")
398            .position(50.0, 50.0)
399            .size(20.0, 20.0)
400            .build();
401
402        index.insert(&node1).unwrap();
403        index.insert(&node2).unwrap();
404        assert_eq!(index.len(), 2);
405
406        // Clear the index
407        index.clear();
408        assert_eq!(index.len(), 0);
409        assert!(index.is_empty());
410
411        // Verify queries return empty results after clear
412        let query_bounds = Rect::new(0.0, 0.0, 100.0, 100.0);
413        let results = index.query_rect(&query_bounds);
414        assert_eq!(results.len(), 0);
415
416        // Verify bounds are None after clear
417        let bounds = index.bounds();
418        assert_eq!(bounds, None);
419    }
420}