diff --git a/src/grid.rs b/src/grid.rs index 79b44e1..3c77d04 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -21,3 +21,48 @@ impl Grid { self.cells[y * self.width + x] = v; } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_initializes_to_zero() { + let grid = Grid::new(4, 3); + assert_eq!(grid.width, 4); + assert_eq!(grid.height, 3); + assert!(grid.cells.iter().all(|&v| v == 0.0)); + } + + #[test] + fn new_allocates_correct_cell_count() { + let grid = Grid::new(5, 7); + assert_eq!(grid.cells.len(), 35); + } + + #[test] + fn set_and_get_roundtrip() { + let mut grid = Grid::new(4, 4); + grid.set(2, 3, 1.5); + assert_eq!(grid.get(2, 3), 1.5); + } + + #[test] + fn set_uses_row_major_indexing() { + let mut grid = Grid::new(4, 4); + grid.set(1, 2, 9.9); + // y * width + x = 2 * 4 + 1 = 9 + assert_eq!(grid.cells[9], 9.9); + } + + #[test] + fn set_does_not_clobber_neighbors() { + let mut grid = Grid::new(3, 3); + grid.set(0, 0, 1.0); + grid.set(1, 0, 2.0); + grid.set(0, 1, 3.0); + assert_eq!(grid.get(0, 0), 1.0); + assert_eq!(grid.get(1, 0), 2.0); + assert_eq!(grid.get(0, 1), 3.0); + } +} diff --git a/src/renderer.rs b/src/renderer.rs index d6ebe41..86009d2 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -42,3 +42,46 @@ pub fn render(grid: &Grid, time: f32, stdout: &mut impl Write) -> std::io::Resul Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn lava_color_zero_is_black() { + let Color::Rgb { r, g, b } = lava_color(0.0) else { + panic!("expected Rgb variant"); + }; + assert_eq!((r, g, b), (0, 0, 0)); + } + + #[test] + fn lava_color_clamps_large_input() { + // v=100 * 0.35 = 35, clamped to 1.0 → same as v where v*0.35 == 1.0 + let Color::Rgb { r: r1, g: g1, b: b1 } = lava_color(100.0) else { + panic!("expected Rgb variant"); + }; + let Color::Rgb { r: r2, g: g2, b: b2 } = lava_color(3.0) else { + panic!("expected Rgb variant"); + }; + assert_eq!((r1, g1, b1), (r2, g2, b2)); + } + + #[test] + fn lava_color_red_dominates_at_low_values() { + // At low (but nonzero) t, red > green > blue due to polynomial degrees + let Color::Rgb { r, g, b } = lava_color(1.0) else { + panic!("expected Rgb variant"); + }; + assert!(r >= g); + assert!(g >= b); + } + + #[test] + fn render_writes_output() { + let grid = Grid::new(4, 4); + let mut buf: Vec = Vec::new(); + render(&grid, 0.0, &mut buf).unwrap(); + assert!(!buf.is_empty()); + } +} diff --git a/src/simulation.rs b/src/simulation.rs index f356533..20dafd7 100644 --- a/src/simulation.rs +++ b/src/simulation.rs @@ -50,3 +50,92 @@ pub fn update_grid(grid: &mut Grid, balls: &[Metaball]) { } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn make_ball(x: f32, y: f32, vx: f32, vy: f32, radius: f32) -> Metaball { + Metaball { x, y, vx, vy, radius } + } + + // clamp_and_bounce tests (tested via update_balls) + + #[test] + fn ball_moves_by_velocity() { + let mut balls = vec![make_ball(10.0, 10.0, 2.0, -3.0, 1.0)]; + update_balls(&mut balls, 100.0, 100.0); + assert_eq!(balls[0].x, 12.0); + assert_eq!(balls[0].y, 7.0); + } + + #[test] + fn ball_bounces_off_right_wall() { + // Place ball near right wall with positive vx so it would exceed max + let mut balls = vec![make_ball(98.0, 50.0, 5.0, 0.0, 2.0)]; + update_balls(&mut balls, 100.0, 100.0); + // After update: x = 103 > 98 (100 - radius), so clamp to 98 and reverse vx + assert_eq!(balls[0].x, 98.0); + assert!(balls[0].vx < 0.0); + } + + #[test] + fn ball_bounces_off_left_wall() { + let mut balls = vec![make_ball(3.0, 50.0, -5.0, 0.0, 4.0)]; + update_balls(&mut balls, 100.0, 100.0); + // After update: x = -2 < 4 (radius), clamp to 4 and flip vx positive + assert_eq!(balls[0].x, 4.0); + assert!(balls[0].vx > 0.0); + } + + #[test] + fn ball_bounces_off_bottom_wall() { + let mut balls = vec![make_ball(50.0, 97.0, 0.0, 5.0, 3.0)]; + update_balls(&mut balls, 100.0, 100.0); + assert_eq!(balls[0].y, 97.0); + assert!(balls[0].vy < 0.0); + } + + #[test] + fn ball_bounces_off_top_wall() { + let mut balls = vec![make_ball(50.0, 2.0, 0.0, -5.0, 3.0)]; + update_balls(&mut balls, 100.0, 100.0); + assert_eq!(balls[0].y, 3.0); + assert!(balls[0].vy > 0.0); + } + + #[test] + fn field_value_is_higher_near_ball_center() { + let balls = vec![make_ball(5.0, 5.0, 0.0, 0.0, 10.0)]; + // Cell directly on ball center should have high field value + let near = field_value(5.0, 5.0, &balls); + // Cell far from ball + let far = field_value(50.0, 50.0, &balls); + assert!(near > far); + } + + #[test] + fn field_value_no_balls_returns_negative_threshold() { + let value = field_value(0.0, 0.0, &[]); + assert_eq!(value, -0.8); + } + + #[test] + fn update_grid_populates_all_cells() { + let mut grid = Grid::new(5, 5); + let balls = vec![make_ball(2.0, 2.0, 0.0, 0.0, 5.0)]; + update_grid(&mut grid, &balls); + // At least some cells should be non-zero + assert!(grid.cells.iter().any(|&v| v != 0.0)); + } + + #[test] + fn update_grid_center_cell_higher_than_edge() { + let mut grid = Grid::new(10, 10); + let balls = vec![make_ball(5.0, 5.0, 0.0, 0.0, 10.0)]; + update_grid(&mut grid, &balls); + let center = grid.get(5, 5); + let edge = grid.get(0, 0); + assert!(center > edge); + } +}