oecs-sharp/OECS.Tests/CoreTests.cs

268 lines
7.6 KiB
C#
Raw Permalink Normal View History

using FluentAssertions;
using Xunit;
namespace OECS.Tests;
public class EntityTests
{
[Fact]
public void CreateEntity_ReturnsUniqueIds()
{
var world = new World();
var a = world.CreateEntity();
var b = world.CreateEntity();
a.Should().NotBe(b);
a.Id.Should().NotBe(b.Id);
}
[Fact]
public void Null_IsSentinel()
{
Entity.Null.IsNull.Should().BeTrue();
Entity.Null.Id.Should().Be(0);
Entity.Null.Version.Should().Be(0);
}
[Fact]
public void DestroyEntity_RecyclesId_WithIncrementedVersion()
{
var world = new World();
var original = world.CreateEntity();
uint originalId = original.Id;
uint originalVersion = original.Version;
world.DestroyEntity(original);
world.IsAlive(original).Should().BeFalse();
var recycled = world.CreateEntity();
recycled.Id.Should().Be(originalId);
recycled.Version.Should().BeGreaterThan(originalVersion);
}
[Fact]
public void IsAlive_ReturnsFalse_ForDestroyedEntity()
{
var world = new World();
var entity = world.CreateEntity();
world.IsAlive(entity).Should().BeTrue();
world.DestroyEntity(entity);
world.IsAlive(entity).Should().BeFalse();
}
[Fact]
public void IsAlive_ReturnsFalse_ForNull()
{
var world = new World();
world.IsAlive(Entity.Null).Should().BeFalse();
}
[Fact]
public void ToString_IncludesIdAndVersion()
{
var world = new World();
var entity = world.CreateEntity();
var str = entity.ToString();
str.Should().Contain(entity.Id.ToString());
str.Should().Contain(entity.Version.ToString());
}
[Fact]
public void Equality_ChecksBothIdAndVersion()
{
var world = new World();
var a = world.CreateEntity();
world.DestroyEntity(a);
var b = world.CreateEntity(); // Same ID, different version
a.Should().NotBe(b);
(a == b).Should().BeFalse();
}
}
public class ComponentTests
{
private struct Position { public float X; public float Y; }
private struct Velocity { public float X; public float Y; }
private struct Health { public int Value; }
[Fact]
public void AddAndGetComponent_RoundTrips()
{
var world = new World();
var entity = world.CreateEntity();
world.AddComponent(entity, new Position { X = 1, Y = 2 });
ref var pos = ref world.GetComponent<Position>(entity);
pos.X.Should().Be(1);
pos.Y.Should().Be(2);
}
[Fact]
public void AddComponent_ReplacesExisting()
{
var world = new World();
var entity = world.CreateEntity();
world.AddComponent(entity, new Position { X = 1, Y = 2 });
world.AddComponent(entity, new Position { X = 3, Y = 4 });
ref var pos = ref world.GetComponent<Position>(entity);
pos.X.Should().Be(3);
pos.Y.Should().Be(4);
}
[Fact]
public void HasComponent_ReturnsCorrectly()
{
var world = new World();
var entity = world.CreateEntity();
world.HasComponent<Position>(entity).Should().BeFalse();
world.AddComponent(entity, new Position());
world.HasComponent<Position>(entity).Should().BeTrue();
}
[Fact]
public void RemoveComponent_RemovesIt()
{
var world = new World();
var entity = world.CreateEntity();
world.AddComponent(entity, new Position { X = 1, Y = 2 });
world.RemoveComponent<Position>(entity);
world.HasComponent<Position>(entity).Should().BeFalse();
}
[Fact]
public void RemoveComponent_NoOp_WhenNotPresent()
{
var world = new World();
var entity = world.CreateEntity();
// Should not throw.
world.RemoveComponent<Position>(entity);
}
[Fact]
public void MultipleComponents_OnSameEntity()
{
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 });
world.HasComponent<Position>(entity).Should().BeTrue();
world.HasComponent<Velocity>(entity).Should().BeTrue();
world.HasComponent<Health>(entity).Should().BeTrue();
ref var pos = ref world.GetComponent<Position>(entity);
ref var vel = ref world.GetComponent<Velocity>(entity);
ref var hp = ref world.GetComponent<Health>(entity);
pos.X.Should().Be(1);
vel.Y.Should().Be(4);
hp.Value.Should().Be(100);
}
[Fact]
public void DestroyEntity_RemovesAllComponents()
{
var world = new World();
var entity = world.CreateEntity();
world.AddComponent(entity, new Position());
world.AddComponent(entity, new Velocity());
world.DestroyEntity(entity);
// After destroy, the entity is not alive, so component queries
// on the stale handle are irrelevant. But a new entity with the
// same ID should not have the old components.
var newEntity = world.CreateEntity();
world.HasComponent<Position>(newEntity).Should().BeFalse();
world.HasComponent<Velocity>(newEntity).Should().BeFalse();
}
[Fact]
public void GetComponent_Throws_WhenNotPresent()
{
var world = new World();
var entity = world.CreateEntity();
var act = () => world.GetComponent<Position>(entity);
act.Should().Throw<InvalidOperationException>()
.WithMessage("*does not have*Position*");
}
[Fact]
public void GetComponent_Throws_WhenEntityIdOutOfRange()
{
var world = new World();
// Create an entity with a very large ID by exhausting the allocator.
// This is hard to do without internals, so we test via a destroyed
// entity that has a valid ID but no component.
var entity = world.CreateEntity();
var act = () => world.GetComponent<Position>(entity);
act.Should().Throw<InvalidOperationException>()
.WithMessage("*does not have*Position*");
}
[Fact]
public void TryGetComponent_ReturnsTrue_WhenPresent()
{
var world = new World();
var entity = world.CreateEntity();
world.AddComponent(entity, new Position { X = 1, Y = 2 });
bool found = world.TryGetComponent<Position>(entity, out var pos);
found.Should().BeTrue();
pos.X.Should().Be(1);
pos.Y.Should().Be(2);
}
[Fact]
public void TryGetComponent_ReturnsFalse_WhenNotPresent()
{
var world = new World();
var entity = world.CreateEntity();
bool found = world.TryGetComponent<Position>(entity, out var pos);
found.Should().BeFalse();
pos.Should().Be(default(Position));
}
[Fact]
public void AddComponent_Throws_WhenEntityNotAlive()
{
var world = new World();
var entity = world.CreateEntity();
world.DestroyEntity(entity);
var act = () => world.AddComponent(entity, new Position());
act.Should().Throw<InvalidOperationException>();
}
[Fact]
public void Mutation_ViaRef_IsVisible()
{
var world = new World();
var entity = world.CreateEntity();
world.AddComponent(entity, new Position { X = 0, Y = 0 });
ref var pos = ref world.GetComponent<Position>(entity);
pos.X = 42;
ref var pos2 = ref world.GetComponent<Position>(entity);
pos2.X.Should().Be(42);
}
}