test: add tests for item systems & movement types

This commit is contained in:
2025-08-18 00:04:07 -05:00
parent 13a9c165f7
commit 471b118efd
2 changed files with 354 additions and 0 deletions

159
tests/item.rs Normal file
View File

@@ -0,0 +1,159 @@
use pacman::systems::components::EntityType;
// Helper functions that extract the core scoring logic from item_system
// This allows us to test the business rules without ECS complexity
fn calculate_score_for_item(entity_type: EntityType) -> Option<u32> {
match entity_type {
EntityType::Pellet => Some(10),
EntityType::PowerPellet => Some(50),
_ => None,
}
}
fn is_collectible_item(entity_type: EntityType) -> bool {
matches!(entity_type, EntityType::Pellet | EntityType::PowerPellet)
}
fn should_trigger_audio_on_collection(entity_type: EntityType) -> bool {
is_collectible_item(entity_type)
}
#[test]
fn test_pellet_scoring() {
assert_eq!(calculate_score_for_item(EntityType::Pellet), Some(10));
}
#[test]
fn test_power_pellet_scoring() {
assert_eq!(calculate_score_for_item(EntityType::PowerPellet), Some(50));
}
#[test]
fn test_non_collectible_items_no_score() {
assert_eq!(calculate_score_for_item(EntityType::Player), None);
assert_eq!(calculate_score_for_item(EntityType::Ghost), None);
}
#[test]
fn test_collectible_item_detection() {
assert!(is_collectible_item(EntityType::Pellet));
assert!(is_collectible_item(EntityType::PowerPellet));
assert!(!is_collectible_item(EntityType::Player));
assert!(!is_collectible_item(EntityType::Ghost));
}
#[test]
fn test_audio_trigger_for_collectibles() {
assert!(should_trigger_audio_on_collection(EntityType::Pellet));
assert!(should_trigger_audio_on_collection(EntityType::PowerPellet));
assert!(!should_trigger_audio_on_collection(EntityType::Player));
assert!(!should_trigger_audio_on_collection(EntityType::Ghost));
}
#[test]
fn test_score_progression() {
// Test that power pellets are worth more than regular pellets
let pellet_score = calculate_score_for_item(EntityType::Pellet).unwrap();
let power_pellet_score = calculate_score_for_item(EntityType::PowerPellet).unwrap();
assert!(power_pellet_score > pellet_score);
assert_eq!(power_pellet_score / pellet_score, 5); // Power pellets are worth 5x regular pellets
}
#[test]
fn test_entity_type_variants() {
// Test all EntityType variants to ensure they're handled appropriately
let all_types = vec![
EntityType::Player,
EntityType::Ghost,
EntityType::Pellet,
EntityType::PowerPellet,
];
let mut collectible_count = 0;
let mut non_collectible_count = 0;
for entity_type in all_types {
if is_collectible_item(entity_type) {
collectible_count += 1;
// All collectible items should have a score
assert!(calculate_score_for_item(entity_type).is_some());
} else {
non_collectible_count += 1;
// Non-collectible items should not have a score
assert!(calculate_score_for_item(entity_type).is_none());
}
}
// Verify we have the expected number of each type
assert_eq!(collectible_count, 2); // Pellet and PowerPellet
assert_eq!(non_collectible_count, 2); // Player and Ghost
}
#[test]
fn test_score_accumulation() {
// Test score accumulation logic (simulating multiple collections)
let mut total_score = 0u32;
// Collect some items
let collected_items = vec![
EntityType::Pellet,
EntityType::Pellet,
EntityType::PowerPellet,
EntityType::Pellet,
EntityType::PowerPellet,
];
for item in collected_items {
if let Some(score) = calculate_score_for_item(item) {
total_score += score;
}
}
// Expected: 3 pellets (30) + 2 power pellets (100) = 130
assert_eq!(total_score, 130);
}
#[test]
fn test_collision_filtering_logic() {
// Test the logic for determining valid collision pairs
// This mirrors the logic in item_system that checks entity types
let test_cases = vec![
(EntityType::Player, EntityType::Pellet, true),
(EntityType::Player, EntityType::PowerPellet, true),
(EntityType::Player, EntityType::Ghost, false), // Not handled by item system
(EntityType::Player, EntityType::Player, false), // Not a valid collision
(EntityType::Ghost, EntityType::Pellet, false), // Ghosts don't collect items
(EntityType::Pellet, EntityType::PowerPellet, false), // Items don't interact
];
for (entity1, entity2, should_be_valid) in test_cases {
let is_valid_item_collision = (entity1 == EntityType::Player && is_collectible_item(entity2))
|| (entity2 == EntityType::Player && is_collectible_item(entity1));
assert_eq!(
is_valid_item_collision, should_be_valid,
"Failed for collision between {:?} and {:?}",
entity1, entity2
);
}
}
#[test]
fn test_item_collection_side_effects() {
// Test that collecting items should trigger the expected side effects
let collectible_items = vec![EntityType::Pellet, EntityType::PowerPellet];
for item in collectible_items {
// Should provide score
assert!(calculate_score_for_item(item).is_some());
// Should trigger audio
assert!(should_trigger_audio_on_collection(item));
// Should be marked as collectible
assert!(is_collectible_item(item));
}
}

195
tests/movement.rs Normal file
View File

@@ -0,0 +1,195 @@
use glam::Vec2;
use pacman::map::direction::Direction;
use pacman::map::graph::{Graph, Node};
use pacman::systems::movement::{BufferedDirection, Position, Velocity};
fn create_test_graph() -> Graph {
let mut graph = Graph::new();
// Add a few test nodes
let node0 = graph.add_node(Node {
position: Vec2::new(0.0, 0.0),
});
let node1 = graph.add_node(Node {
position: Vec2::new(16.0, 0.0),
});
let node2 = graph.add_node(Node {
position: Vec2::new(0.0, 16.0),
});
// Connect them
graph.connect(node0, node1, false, None, Direction::Right).unwrap();
graph.connect(node0, node2, false, None, Direction::Down).unwrap();
graph
}
#[test]
fn test_position_is_at_node() {
let stopped_pos = Position::Stopped { node: 0 };
let moving_pos = Position::Moving {
from: 0,
to: 1,
remaining_distance: 8.0,
};
assert!(stopped_pos.is_at_node());
assert!(!moving_pos.is_at_node());
}
#[test]
fn test_position_current_node() {
let stopped_pos = Position::Stopped { node: 5 };
let moving_pos = Position::Moving {
from: 3,
to: 7,
remaining_distance: 12.0,
};
assert_eq!(stopped_pos.current_node(), 5);
assert_eq!(moving_pos.current_node(), 3);
}
#[test]
fn test_position_tick_no_movement_when_stopped() {
let mut pos = Position::Stopped { node: 0 };
let result = pos.tick(5.0);
assert!(result.is_none());
assert_eq!(pos, Position::Stopped { node: 0 });
}
#[test]
fn test_position_tick_no_movement_when_zero_distance() {
let mut pos = Position::Moving {
from: 0,
to: 1,
remaining_distance: 10.0,
};
let result = pos.tick(0.0);
assert!(result.is_none());
assert_eq!(
pos,
Position::Moving {
from: 0,
to: 1,
remaining_distance: 10.0,
}
);
}
#[test]
fn test_position_tick_partial_movement() {
let mut pos = Position::Moving {
from: 0,
to: 1,
remaining_distance: 10.0,
};
let result = pos.tick(3.0);
assert!(result.is_none());
assert_eq!(
pos,
Position::Moving {
from: 0,
to: 1,
remaining_distance: 7.0,
}
);
}
#[test]
fn test_position_tick_exact_arrival() {
let mut pos = Position::Moving {
from: 0,
to: 1,
remaining_distance: 5.0,
};
let result = pos.tick(5.0);
assert!(result.is_none());
assert_eq!(pos, Position::Stopped { node: 1 });
}
#[test]
fn test_position_tick_overshoot_with_overflow() {
let mut pos = Position::Moving {
from: 0,
to: 1,
remaining_distance: 3.0,
};
let result = pos.tick(8.0);
assert_eq!(result, Some(5.0));
assert_eq!(pos, Position::Stopped { node: 1 });
}
#[test]
fn test_position_get_pixel_position_stopped() {
let graph = create_test_graph();
let pos = Position::Stopped { node: 0 };
let pixel_pos = pos.get_pixel_position(&graph).unwrap();
let expected = Vec2::new(
0.0 + pacman::constants::BOARD_PIXEL_OFFSET.x as f32,
0.0 + pacman::constants::BOARD_PIXEL_OFFSET.y as f32,
);
assert_eq!(pixel_pos, expected);
}
#[test]
fn test_position_get_pixel_position_moving() {
let graph = create_test_graph();
let pos = Position::Moving {
from: 0,
to: 1,
remaining_distance: 8.0, // Halfway through a 16-unit edge
};
let pixel_pos = pos.get_pixel_position(&graph).unwrap();
// Should be halfway between (0,0) and (16,0), so at (8,0) plus offset
let expected = Vec2::new(
8.0 + pacman::constants::BOARD_PIXEL_OFFSET.x as f32,
0.0 + pacman::constants::BOARD_PIXEL_OFFSET.y as f32,
);
assert_eq!(pixel_pos, expected);
}
#[test]
fn test_velocity_basic_properties() {
let velocity = Velocity {
speed: 2.5,
direction: Direction::Up,
};
assert_eq!(velocity.speed, 2.5);
assert_eq!(velocity.direction, Direction::Up);
}
#[test]
fn test_buffered_direction_none() {
let buffered = BufferedDirection::None;
assert_eq!(buffered, BufferedDirection::None);
}
#[test]
fn test_buffered_direction_some() {
let buffered = BufferedDirection::Some {
direction: Direction::Left,
remaining_time: 0.5,
};
if let BufferedDirection::Some {
direction,
remaining_time,
} = buffered
{
assert_eq!(direction, Direction::Left);
assert_eq!(remaining_time, 0.5);
} else {
panic!("Expected BufferedDirection::Some");
}
}