diff --git a/crates/bevy_ecs/src/bundle/mod.rs b/crates/bevy_ecs/src/bundle/mod.rs index a64940f151..31770fe117 100644 --- a/crates/bevy_ecs/src/bundle/mod.rs +++ b/crates/bevy_ecs/src/bundle/mod.rs @@ -3,6 +3,8 @@ //! This module contains the [`Bundle`] trait and some other helper types. mod impls; +#[cfg(test)] +mod tests; /// Derive the [`Bundle`] trait /// @@ -1925,294 +1927,3 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { } }); } - -#[cfg(test)] -mod tests { - use crate::{ - archetype::ArchetypeCreated, lifecycle::HookContext, prelude::*, world::DeferredWorld, - }; - use alloc::vec; - - #[derive(Component)] - struct A; - - #[derive(Component)] - #[component(on_add = a_on_add, on_insert = a_on_insert, on_replace = a_on_replace, on_remove = a_on_remove)] - struct AMacroHooks; - - fn a_on_add(mut world: DeferredWorld, _: HookContext) { - world.resource_mut::().assert_order(0); - } - - fn a_on_insert(mut world: DeferredWorld, _: HookContext) { - world.resource_mut::().assert_order(1); - } - - fn a_on_replace(mut world: DeferredWorld, _: HookContext) { - world.resource_mut::().assert_order(2); - } - - fn a_on_remove(mut world: DeferredWorld, _: HookContext) { - world.resource_mut::().assert_order(3); - } - - #[derive(Component)] - struct B; - - #[derive(Component)] - struct C; - - #[derive(Component)] - struct D; - - #[derive(Component, Eq, PartialEq, Debug)] - struct V(&'static str); // component with a value - - #[derive(Resource, Default)] - struct R(usize); - - impl R { - #[track_caller] - fn assert_order(&mut self, count: usize) { - assert_eq!(count, self.0); - self.0 += 1; - } - } - - #[derive(Bundle)] - #[bundle(ignore_from_components)] - struct BundleNoExtract { - b: B, - no_from_comp: crate::spawn::SpawnRelatedBundle>, - } - - #[test] - fn can_spawn_bundle_without_extract() { - let mut world = World::new(); - let id = world - .spawn(BundleNoExtract { - b: B, - no_from_comp: Children::spawn(Spawn(C)), - }) - .id(); - - assert!(world.entity(id).get::().is_some()); - } - - #[test] - fn component_hook_order_spawn_despawn() { - let mut world = World::new(); - world.init_resource::(); - world - .register_component_hooks::() - .on_add(|mut world, _| world.resource_mut::().assert_order(0)) - .on_insert(|mut world, _| world.resource_mut::().assert_order(1)) - .on_replace(|mut world, _| world.resource_mut::().assert_order(2)) - .on_remove(|mut world, _| world.resource_mut::().assert_order(3)); - - let entity = world.spawn(A).id(); - world.despawn(entity); - assert_eq!(4, world.resource::().0); - } - - #[test] - fn component_hook_order_spawn_despawn_with_macro_hooks() { - let mut world = World::new(); - world.init_resource::(); - - let entity = world.spawn(AMacroHooks).id(); - world.despawn(entity); - - assert_eq!(4, world.resource::().0); - } - - #[test] - fn component_hook_order_insert_remove() { - let mut world = World::new(); - world.init_resource::(); - world - .register_component_hooks::() - .on_add(|mut world, _| world.resource_mut::().assert_order(0)) - .on_insert(|mut world, _| world.resource_mut::().assert_order(1)) - .on_replace(|mut world, _| world.resource_mut::().assert_order(2)) - .on_remove(|mut world, _| world.resource_mut::().assert_order(3)); - - let mut entity = world.spawn_empty(); - entity.insert(A); - entity.remove::(); - entity.flush(); - assert_eq!(4, world.resource::().0); - } - - #[test] - fn component_hook_order_replace() { - let mut world = World::new(); - world - .register_component_hooks::() - .on_replace(|mut world, _| world.resource_mut::().assert_order(0)) - .on_insert(|mut world, _| { - if let Some(mut r) = world.get_resource_mut::() { - r.assert_order(1); - } - }); - - let entity = world.spawn(A).id(); - world.init_resource::(); - let mut entity = world.entity_mut(entity); - entity.insert(A); - entity.insert_if_new(A); // this will not trigger on_replace or on_insert - entity.flush(); - assert_eq!(2, world.resource::().0); - } - - #[test] - fn component_hook_order_recursive() { - let mut world = World::new(); - world.init_resource::(); - world - .register_component_hooks::() - .on_add(|mut world, context| { - world.resource_mut::().assert_order(0); - world.commands().entity(context.entity).insert(B); - }) - .on_remove(|mut world, context| { - world.resource_mut::().assert_order(2); - world.commands().entity(context.entity).remove::(); - }); - - world - .register_component_hooks::() - .on_add(|mut world, context| { - world.resource_mut::().assert_order(1); - world.commands().entity(context.entity).remove::(); - }) - .on_remove(|mut world, _| { - world.resource_mut::().assert_order(3); - }); - - let entity = world.spawn(A).flush(); - let entity = world.get_entity(entity).unwrap(); - assert!(!entity.contains::()); - assert!(!entity.contains::()); - assert_eq!(4, world.resource::().0); - } - - #[test] - fn component_hook_order_recursive_multiple() { - let mut world = World::new(); - world.init_resource::(); - world - .register_component_hooks::() - .on_add(|mut world, context| { - world.resource_mut::().assert_order(0); - world.commands().entity(context.entity).insert(B).insert(C); - }); - - world - .register_component_hooks::() - .on_add(|mut world, context| { - world.resource_mut::().assert_order(1); - world.commands().entity(context.entity).insert(D); - }); - - world - .register_component_hooks::() - .on_add(|mut world, _| { - world.resource_mut::().assert_order(3); - }); - - world - .register_component_hooks::() - .on_add(|mut world, _| { - world.resource_mut::().assert_order(2); - }); - - world.spawn(A).flush(); - assert_eq!(4, world.resource::().0); - } - - #[test] - fn insert_if_new() { - let mut world = World::new(); - let id = world.spawn(V("one")).id(); - let mut entity = world.entity_mut(id); - entity.insert_if_new(V("two")); - entity.insert_if_new((A, V("three"))); - entity.flush(); - // should still contain "one" - let entity = world.entity(id); - assert!(entity.contains::()); - assert_eq!(entity.get(), Some(&V("one"))); - } - - #[derive(Component, Debug, Eq, PartialEq)] - #[component(storage = "SparseSet")] - pub struct SparseV(&'static str); - - #[derive(Component, Debug, Eq, PartialEq)] - #[component(storage = "SparseSet")] - pub struct SparseA; - - #[test] - fn sparse_set_insert_if_new() { - let mut world = World::new(); - let id = world.spawn(SparseV("one")).id(); - let mut entity = world.entity_mut(id); - entity.insert_if_new(SparseV("two")); - entity.insert_if_new((SparseA, SparseV("three"))); - entity.flush(); - // should still contain "one" - let entity = world.entity(id); - assert!(entity.contains::()); - assert_eq!(entity.get(), Some(&SparseV("one"))); - } - - #[test] - fn sorted_remove() { - let mut a = vec![1, 2, 3, 4, 5, 6, 7]; - let b = vec![1, 2, 3, 5, 7]; - super::sorted_remove(&mut a, &b); - - assert_eq!(a, vec![4, 6]); - - let mut a = vec![1]; - let b = vec![1]; - super::sorted_remove(&mut a, &b); - - assert_eq!(a, vec![]); - - let mut a = vec![1]; - let b = vec![2]; - super::sorted_remove(&mut a, &b); - - assert_eq!(a, vec![1]); - } - - #[test] - fn new_archetype_created() { - let mut world = World::new(); - #[derive(Resource, Default)] - struct Count(u32); - world.init_resource::(); - world.add_observer(|_t: On, mut count: ResMut| { - count.0 += 1; - }); - - let mut e = world.spawn((A, B)); - e.insert(C); - e.remove::(); - e.insert(A); - e.insert(A); - - assert_eq!(world.resource::().0, 3); - } - - #[derive(Bundle)] - #[expect(unused, reason = "tests the output of the derive macro is valid")] - struct Ignore { - #[bundle(ignore)] - foo: i32, - #[bundle(ignore)] - bar: i32, - } -} diff --git a/crates/bevy_ecs/src/bundle/tests.rs b/crates/bevy_ecs/src/bundle/tests.rs new file mode 100644 index 0000000000..ec73127eb3 --- /dev/null +++ b/crates/bevy_ecs/src/bundle/tests.rs @@ -0,0 +1,287 @@ +use crate::{ + archetype::ArchetypeCreated, lifecycle::HookContext, prelude::*, world::DeferredWorld, +}; +use alloc::vec; + +#[derive(Component)] +struct A; + +#[derive(Component)] +#[component(on_add = a_on_add, on_insert = a_on_insert, on_replace = a_on_replace, on_remove = a_on_remove)] +struct AMacroHooks; + +fn a_on_add(mut world: DeferredWorld, _: HookContext) { + world.resource_mut::().assert_order(0); +} + +fn a_on_insert(mut world: DeferredWorld, _: HookContext) { + world.resource_mut::().assert_order(1); +} + +fn a_on_replace(mut world: DeferredWorld, _: HookContext) { + world.resource_mut::().assert_order(2); +} + +fn a_on_remove(mut world: DeferredWorld, _: HookContext) { + world.resource_mut::().assert_order(3); +} + +#[derive(Component)] +struct B; + +#[derive(Component)] +struct C; + +#[derive(Component)] +struct D; + +#[derive(Component, Eq, PartialEq, Debug)] +struct V(&'static str); // component with a value + +#[derive(Resource, Default)] +struct R(usize); + +impl R { + #[track_caller] + fn assert_order(&mut self, count: usize) { + assert_eq!(count, self.0); + self.0 += 1; + } +} + +#[derive(Bundle)] +#[bundle(ignore_from_components)] +struct BundleNoExtract { + b: B, + no_from_comp: crate::spawn::SpawnRelatedBundle>, +} + +#[test] +fn can_spawn_bundle_without_extract() { + let mut world = World::new(); + let id = world + .spawn(BundleNoExtract { + b: B, + no_from_comp: Children::spawn(Spawn(C)), + }) + .id(); + + assert!(world.entity(id).get::().is_some()); +} + +#[test] +fn component_hook_order_spawn_despawn() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component_hooks::() + .on_add(|mut world, _| world.resource_mut::().assert_order(0)) + .on_insert(|mut world, _| world.resource_mut::().assert_order(1)) + .on_replace(|mut world, _| world.resource_mut::().assert_order(2)) + .on_remove(|mut world, _| world.resource_mut::().assert_order(3)); + + let entity = world.spawn(A).id(); + world.despawn(entity); + assert_eq!(4, world.resource::().0); +} + +#[test] +fn component_hook_order_spawn_despawn_with_macro_hooks() { + let mut world = World::new(); + world.init_resource::(); + + let entity = world.spawn(AMacroHooks).id(); + world.despawn(entity); + + assert_eq!(4, world.resource::().0); +} + +#[test] +fn component_hook_order_insert_remove() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component_hooks::() + .on_add(|mut world, _| world.resource_mut::().assert_order(0)) + .on_insert(|mut world, _| world.resource_mut::().assert_order(1)) + .on_replace(|mut world, _| world.resource_mut::().assert_order(2)) + .on_remove(|mut world, _| world.resource_mut::().assert_order(3)); + + let mut entity = world.spawn_empty(); + entity.insert(A); + entity.remove::(); + entity.flush(); + assert_eq!(4, world.resource::().0); +} + +#[test] +fn component_hook_order_replace() { + let mut world = World::new(); + world + .register_component_hooks::() + .on_replace(|mut world, _| world.resource_mut::().assert_order(0)) + .on_insert(|mut world, _| { + if let Some(mut r) = world.get_resource_mut::() { + r.assert_order(1); + } + }); + + let entity = world.spawn(A).id(); + world.init_resource::(); + let mut entity = world.entity_mut(entity); + entity.insert(A); + entity.insert_if_new(A); // this will not trigger on_replace or on_insert + entity.flush(); + assert_eq!(2, world.resource::().0); +} + +#[test] +fn component_hook_order_recursive() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component_hooks::() + .on_add(|mut world, context| { + world.resource_mut::().assert_order(0); + world.commands().entity(context.entity).insert(B); + }) + .on_remove(|mut world, context| { + world.resource_mut::().assert_order(2); + world.commands().entity(context.entity).remove::(); + }); + + world + .register_component_hooks::() + .on_add(|mut world, context| { + world.resource_mut::().assert_order(1); + world.commands().entity(context.entity).remove::(); + }) + .on_remove(|mut world, _| { + world.resource_mut::().assert_order(3); + }); + + let entity = world.spawn(A).flush(); + let entity = world.get_entity(entity).unwrap(); + assert!(!entity.contains::()); + assert!(!entity.contains::()); + assert_eq!(4, world.resource::().0); +} + +#[test] +fn component_hook_order_recursive_multiple() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component_hooks::() + .on_add(|mut world, context| { + world.resource_mut::().assert_order(0); + world.commands().entity(context.entity).insert(B).insert(C); + }); + + world + .register_component_hooks::() + .on_add(|mut world, context| { + world.resource_mut::().assert_order(1); + world.commands().entity(context.entity).insert(D); + }); + + world + .register_component_hooks::() + .on_add(|mut world, _| { + world.resource_mut::().assert_order(3); + }); + + world + .register_component_hooks::() + .on_add(|mut world, _| { + world.resource_mut::().assert_order(2); + }); + + world.spawn(A).flush(); + assert_eq!(4, world.resource::().0); +} + +#[test] +fn insert_if_new() { + let mut world = World::new(); + let id = world.spawn(V("one")).id(); + let mut entity = world.entity_mut(id); + entity.insert_if_new(V("two")); + entity.insert_if_new((A, V("three"))); + entity.flush(); + // should still contain "one" + let entity = world.entity(id); + assert!(entity.contains::()); + assert_eq!(entity.get(), Some(&V("one"))); +} + +#[derive(Component, Debug, Eq, PartialEq)] +#[component(storage = "SparseSet")] +pub struct SparseV(&'static str); + +#[derive(Component, Debug, Eq, PartialEq)] +#[component(storage = "SparseSet")] +pub struct SparseA; + +#[test] +fn sparse_set_insert_if_new() { + let mut world = World::new(); + let id = world.spawn(SparseV("one")).id(); + let mut entity = world.entity_mut(id); + entity.insert_if_new(SparseV("two")); + entity.insert_if_new((SparseA, SparseV("three"))); + entity.flush(); + // should still contain "one" + let entity = world.entity(id); + assert!(entity.contains::()); + assert_eq!(entity.get(), Some(&SparseV("one"))); +} + +#[test] +fn sorted_remove() { + let mut a = vec![1, 2, 3, 4, 5, 6, 7]; + let b = vec![1, 2, 3, 5, 7]; + super::sorted_remove(&mut a, &b); + + assert_eq!(a, vec![4, 6]); + + let mut a = vec![1]; + let b = vec![1]; + super::sorted_remove(&mut a, &b); + + assert_eq!(a, vec![]); + + let mut a = vec![1]; + let b = vec![2]; + super::sorted_remove(&mut a, &b); + + assert_eq!(a, vec![1]); +} + +#[test] +fn new_archetype_created() { + let mut world = World::new(); + #[derive(Resource, Default)] + struct Count(u32); + world.init_resource::(); + world.add_observer(|_t: On, mut count: ResMut| { + count.0 += 1; + }); + + let mut e = world.spawn((A, B)); + e.insert(C); + e.remove::(); + e.insert(A); + e.insert(A); + + assert_eq!(world.resource::().0, 3); +} + +#[derive(Bundle)] +#[expect(unused, reason = "tests the output of the derive macro is valid")] +struct Ignore { + #[bundle(ignore)] + foo: i32, + #[bundle(ignore)] + bar: i32, +}