using FluentAssertions; using Xunit; namespace OECS.Tests; public class QueryTests { private struct Position { public float X; public float Y; } private struct Velocity { public float X; public float Y; } private struct Health { public int Value; } private struct Frozen { } [Fact] public void Query_WithSingleComponent_ReturnsMatchingEntities() { var world = new World(); var a = world.CreateEntity(); var b = world.CreateEntity(); var c = world.CreateEntity(); world.AddComponent(a, new Position { X = 1, Y = 2 }); world.AddComponent(b, new Position { X = 3, Y = 4 }); // c has no Position var results = new List(); foreach (var it in world.Select()) results.Add(it.Entity); results.Should().BeEquivalentTo([a, b]); } [Fact] public void Query_WithMultipleComponents_ReturnsIntersection() { var world = new World(); var a = world.CreateEntity(); var b = world.CreateEntity(); var c = world.CreateEntity(); world.AddComponent(a, new Position { X = 1, Y = 2 }); world.AddComponent(a, new Velocity { X = 1, Y = 0 }); world.AddComponent(b, new Position { X = 3, Y = 4 }); // b has no Velocity world.AddComponent(c, new Velocity { X = 0, Y = 1 }); // c has no Position var results = new List(); foreach (var it in world.Select()) results.Add(it.Entity); results.Should().BeEquivalentTo([a]); } [Fact] public void Query_Without_ExcludesEntities() { var world = new World(); var a = world.CreateEntity(); var b = world.CreateEntity(); world.AddComponent(a, new Position { X = 1, Y = 2 }); world.AddComponent(b, new Position { X = 3, Y = 4 }); world.AddComponent(b, new Frozen()); var query = new Query().Without(); var results = new List(); foreach (var it in world.Select(query)) results.Add(it.Entity); results.Should().BeEquivalentTo([a]); } [Fact] public void Query_EmptyResult_WhenNoEntitiesMatch() { var world = new World(); var count = 0; foreach (var it in world.Select()) count++; count.Should().Be(0); } [Fact] public void Query_RefMutation_IsVisible() { var world = new World(); var entity = world.CreateEntity(); world.AddComponent(entity, new Position { X = 0, Y = 0 }); foreach (var it in world.Select()) it.Item1.X = 42; ref var pos = ref world.GetComponent(entity); pos.X.Should().Be(42); } [Fact] public void Query_DestroyedEntities_DoNotAppear() { var world = new World(); var a = world.CreateEntity(); var b = world.CreateEntity(); world.AddComponent(a, new Position { X = 1, Y = 2 }); world.AddComponent(b, new Position { X = 3, Y = 4 }); world.DestroyEntity(a); var results = new List(); foreach (var it in world.Select()) results.Add(it.Entity); results.Should().BeEquivalentTo([b]); } [Fact] public void Query_ThreeComponents_Works() { var world = new World(); var entity = world.CreateEntity(); world.AddComponent(entity, new Position { X = 1, Y = 2 }); world.AddComponent(entity, new Velocity { X = 3, Y = 4 }); world.AddComponent(entity, new Health { Value = 100 }); var count = 0; foreach (var it in world.Select()) { count++; it.Item1.X.Should().Be(1); it.Item2.Y.Should().Be(4); it.Item3.Value.Should().Be(100); } count.Should().Be(1); } [Fact] public void FindEntity_ReturnsFirstMatch() { var world = new World(); var a = world.CreateEntity(); var b = world.CreateEntity(); world.AddComponent(a, new Position { X = 1, Y = 2 }); world.AddComponent(b, new Position { X = 3, Y = 4 }); var found = world.FindEntity(); found.Should().NotBe(Entity.Null); } } public class SystemTests { private struct Position { public float X; public float Y; } private struct Velocity { public float X; public float Y; } private class MovementSystem : ITickedSystem { public void Run(World world) => Run(world, Tick.Logical()); public void Run(World world, Tick tick) { float dt = tick.DeltaTime; foreach (var it in world.Select()) { it.Item1.X += it.Item2.X * dt; it.Item1.Y += it.Item2.Y * dt; } } } [Fact] public void System_RunTimed_UpdatesComponents() { var world = new World(); var group = new SystemGroup(world); group.Add(new MovementSystem()); var entity = world.CreateEntity(); world.AddComponent(entity, new Position { X = 0, Y = 0 }); world.AddComponent(entity, new Velocity { X = 1, Y = 2 }); group.RunTimed(0.5f); ref var pos = ref world.GetComponent(entity); pos.X.Should().Be(0.5f); pos.Y.Should().Be(1.0f); } [Fact] public void System_RunLogical_DoesNotUseDeltaTime() { var world = new World(); var group = new SystemGroup(world); var system = new TestLogicalSystem(); group.Add(system); group.RunLogical(); system.WasCalled.Should().BeTrue(); system.ReceivedTick.Type.Should().Be(TickType.Logical); system.ReceivedTick.DeltaTime.Should().Be(0); } private class TestLogicalSystem : ITickedSystem { public bool WasCalled { get; private set; } public Tick ReceivedTick { get; private set; } public void Run(World world) { } public void Run(World world, Tick tick) { WasCalled = true; ReceivedTick = tick; } } }