use bevy_debugger_mcp::brp_messages::EntityData;
use bevy_debugger_mcp::state_diff::{ChangeType, FuzzyCompareConfig, GameRules};
use bevy_debugger_mcp::tools::observe::ObserveState;
use serde_json::json;
fn create_test_entity(id: u64, components: Vec<(&str, serde_json::Value)>) -> EntityData {
EntityData {
id,
components: components
.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect(),
}
}
#[test]
fn test_observe_state_creation() {
let state = ObserveState::new();
assert_eq!(state.max_history_size(), 10);
assert!(!state.has_last_snapshot());
assert_eq!(state.history_size(), 0);
}
#[test]
fn test_observe_state_custom_config() {
let fuzzy_config = FuzzyCompareConfig {
epsilon: 1e-3,
relative_tolerance: 1e-6,
};
let game_rules = GameRules::default();
let state = ObserveState::with_diff_config(fuzzy_config, game_rules);
assert!(!state.has_last_snapshot());
assert_eq!(state.history_size(), 0);
}
#[test]
fn test_snapshot_addition_and_history_management() {
let mut state = ObserveState::new();
let entities1 = vec![create_test_entity(
1,
vec![("Transform", json!({"x": 0.0}))],
)];
let snapshot1 = state.add_snapshot(entities1);
assert!(state.has_last_snapshot());
assert_eq!(state.history_size(), 1);
assert_eq!(snapshot1.generation, 1);
let entities2 = vec![create_test_entity(
1,
vec![("Transform", json!({"x": 1.0}))],
)];
let snapshot2 = state.add_snapshot(entities2);
assert_eq!(state.history_size(), 2);
assert_eq!(snapshot2.generation, 2);
}
#[test]
fn test_history_size_limit() {
let mut state = ObserveState::new();
for i in 0..15 {
let entities = vec![create_test_entity(1, vec![("Position", json!(i))])];
state.add_snapshot(entities);
}
assert_eq!(state.history_size(), 10);
}
#[test]
fn test_diff_against_last_snapshot() {
let mut state = ObserveState::new();
let entities1 = vec![create_test_entity(
1,
vec![("Transform", json!({"x": 0.0}))],
)];
state.add_snapshot(entities1);
let entities2 = vec![create_test_entity(
1,
vec![("Transform", json!({"x": 1.0}))],
)];
let snapshot2 = state.create_snapshot(entities2);
let diff_result = state.diff_against_last(&snapshot2);
assert!(diff_result.is_some());
let result = diff_result.unwrap();
assert_eq!(result.changes.len(), 1);
assert_eq!(result.changes[0].change_type, ChangeType::ComponentModified);
assert_eq!(result.changes[0].entity_id, 1);
}
#[test]
fn test_diff_against_last_no_previous() {
let mut state = ObserveState::new();
let entities = vec![create_test_entity(
1,
vec![("Transform", json!({"x": 0.0}))],
)];
let snapshot = state.create_snapshot(entities);
let diff_result = state.diff_against_last(&snapshot);
assert!(diff_result.is_none());
}
#[test]
fn test_diff_against_history_by_index() {
let mut state = ObserveState::new();
let entities1 = vec![create_test_entity(1, vec![("Position", json!(0))])];
state.add_snapshot(entities1);
let entities2 = vec![create_test_entity(1, vec![("Position", json!(5))])];
state.add_snapshot(entities2);
let entities3 = vec![create_test_entity(1, vec![("Position", json!(10))])];
let snapshot3 = state.create_snapshot(entities3);
let diff_result = state.diff_against_history(&snapshot3, 0);
assert!(diff_result.is_some());
let result = diff_result.unwrap();
assert_eq!(result.changes.len(), 1);
assert_eq!(result.changes[0].change_type, ChangeType::ComponentModified);
if let (Some(old_val), Some(new_val)) =
(&result.changes[0].old_value, &result.changes[0].new_value)
{
assert_eq!(old_val, &json!(0));
assert_eq!(new_val, &json!(10));
}
}
#[test]
fn test_diff_against_history_invalid_index() {
let mut state = ObserveState::new();
let entities = vec![create_test_entity(1, vec![("Position", json!(0))])];
let snapshot = state.add_snapshot(entities);
let diff_result = state.diff_against_history(&snapshot, 5);
assert!(diff_result.is_none());
}
#[test]
fn test_configure_diff_engine() {
let mut state = ObserveState::new();
let new_config = FuzzyCompareConfig {
epsilon: 1e-2,
relative_tolerance: 1e-5,
};
let mut new_rules = GameRules::default();
new_rules.max_position_change_per_second = Some(50.0);
state.configure_diff(new_config, new_rules);
let entities1 = vec![create_test_entity(1, vec![("Position", json!(1.0))])];
state.add_snapshot(entities1);
let entities2 = vec![create_test_entity(1, vec![("Position", json!(1.005))])]; let snapshot2 = state.add_snapshot(entities2);
let diff_result = state.diff_against_last(&snapshot2);
assert!(diff_result.is_some());
let result = diff_result.unwrap();
assert_eq!(result.changes.len(), 0);
}
#[test]
fn test_clear_history() {
let mut state = ObserveState::new();
for i in 0..5 {
let entities = vec![create_test_entity(1, vec![("Position", json!(i))])];
state.add_snapshot(entities);
}
assert_eq!(state.history_size(), 5);
assert!(state.has_last_snapshot());
state.clear_history();
assert_eq!(state.history_size(), 0);
assert!(!state.has_last_snapshot());
}
#[test]
fn test_complex_entity_changes() {
let mut state = ObserveState::new();
let entities1 = vec![
create_test_entity(1, vec![("Transform", json!({"x": 0.0, "y": 0.0}))]),
create_test_entity(2, vec![("Health", json!(100))]),
];
state.add_snapshot(entities1);
let entities2 = vec![
create_test_entity(
1,
vec![
("Transform", json!({"x": 1.0, "y": 0.0})),
("Velocity", json!({"vx": 1.0, "vy": 0.0})), ],
),
create_test_entity(3, vec![("Health", json!(50))]), ];
let snapshot2 = state.create_snapshot(entities2);
let diff_result = state.diff_against_last(&snapshot2);
assert!(diff_result.is_some());
let result = diff_result.unwrap();
assert_eq!(result.changes.len(), 4);
let added_entities: Vec<_> = result.filter_by_type(ChangeType::EntityAdded);
let removed_entities: Vec<_> = result.filter_by_type(ChangeType::EntityRemoved);
let modified_components: Vec<_> = result.filter_by_type(ChangeType::ComponentModified);
let added_components: Vec<_> = result.filter_by_type(ChangeType::ComponentAdded);
assert_eq!(added_entities.len(), 1);
assert_eq!(removed_entities.len(), 1);
assert_eq!(modified_components.len(), 1);
assert_eq!(added_components.len(), 1);
assert_eq!(added_entities[0].entity_id, 3);
assert_eq!(removed_entities[0].entity_id, 2);
assert_eq!(modified_components[0].entity_id, 1);
assert_eq!(added_components[0].entity_id, 1);
}
#[test]
fn test_rate_of_change_calculation() {
let mut state = ObserveState::new();
let entities1 = vec![create_test_entity(1, vec![("Position", json!(0.0))])];
state.add_snapshot(entities1);
std::thread::sleep(std::time::Duration::from_millis(100));
let entities2 = vec![create_test_entity(1, vec![("Position", json!(10.0))])];
let snapshot2 = state.create_snapshot(entities2);
let diff_result = state.diff_against_last(&snapshot2);
assert!(diff_result.is_some());
let result = diff_result.unwrap();
assert_eq!(result.changes.len(), 1);
let change = &result.changes[0];
assert!(change.rate_of_change.is_some());
let rate = change.rate_of_change.unwrap();
assert!(rate > 0.0);
}
#[test]
fn test_unexpected_change_detection() {
let mut state = ObserveState::new();
let fuzzy_config = FuzzyCompareConfig::default();
let mut game_rules = GameRules::default();
game_rules.max_position_change_per_second = Some(5.0);
state.configure_diff(fuzzy_config, game_rules);
let entities1 = vec![create_test_entity(1, vec![("Transform", json!(0.0))])];
state.add_snapshot(entities1);
std::thread::sleep(std::time::Duration::from_millis(50));
let entities2 = vec![create_test_entity(1, vec![("Transform", json!(100.0))])];
let snapshot2 = state.create_snapshot(entities2);
let diff_result = state.diff_against_last(&snapshot2);
assert!(diff_result.is_some());
let result = diff_result.unwrap();
assert_eq!(result.changes.len(), 1);
let unexpected_changes = result.unexpected_changes();
}
#[test]
fn test_empty_snapshot_handling() {
let mut state = ObserveState::new();
let snapshot1 = state.add_snapshot(vec![]);
let snapshot2 = state.add_snapshot(vec![]);
let diff_result = state.diff_against_last(&snapshot2);
assert!(diff_result.is_some());
let result = diff_result.unwrap();
assert_eq!(result.changes.len(), 0); }
#[test]
fn test_entity_id_consistency() {
let mut state = ObserveState::new();
let entities1 = vec![create_test_entity(42, vec![("Name", json!("TestEntity"))])];
state.add_snapshot(entities1);
let entities2 = vec![create_test_entity(
42,
vec![("Name", json!("ModifiedEntity"))],
)];
let snapshot2 = state.create_snapshot(entities2);
let diff_result = state.diff_against_last(&snapshot2);
assert!(diff_result.is_some());
let result = diff_result.unwrap();
assert_eq!(result.changes.len(), 1);
assert_eq!(result.changes[0].entity_id, 42);
assert_eq!(result.changes[0].change_type, ChangeType::ComponentModified);
}
#[test]
fn test_multiple_history_diffs() {
let mut state = ObserveState::new();
for i in 0..5 {
let entities = vec![create_test_entity(1, vec![("Counter", json!(i))])];
state.add_snapshot(entities);
}
let current_entities = vec![create_test_entity(1, vec![("Counter", json!(10))])];
let current_snapshot = state.create_snapshot(current_entities);
for i in 0..5 {
let diff_result = state.diff_against_history(¤t_snapshot, i);
assert!(diff_result.is_some());
let result = diff_result.unwrap();
assert_eq!(result.changes.len(), 1);
assert_eq!(result.changes[0].change_type, ChangeType::ComponentModified);
if let Some(old_val) = &result.changes[0].old_value {
assert_eq!(old_val, &json!(i));
}
if let Some(new_val) = &result.changes[0].new_value {
assert_eq!(new_val, &json!(10));
}
}
}