Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions Forge.Tests/Statescript/Resolvers/AllResolverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright © Gamesmiths Guild.

using FluentAssertions;
using Gamesmiths.Forge.Statescript;
using Gamesmiths.Forge.Statescript.Properties;

namespace Gamesmiths.Forge.Tests.Statescript.Resolvers;

public class AllResolverTests
{
[Fact]
[Trait("Resolver", "All")]
public void All_resolver_returns_true_when_every_element_matches_the_predicate()
{
var context = new GraphContext();
context.GraphVariables.DefineArrayVariable(
"numbers",
[new Variant128(3), new Variant128(2)]);

var resolver = new AllResolver(
new ArrayVariableResolver("numbers", typeof(int)),
new ComparisonResolver(
new ElementValueResolver(typeof(int)),
ComparisonOperation.GreaterThan,
new VariantResolver(new Variant128(1), typeof(int))));

resolver.Resolve(context).AsBool().Should().BeTrue();
}

[Fact]
[Trait("Resolver", "All")]
public void All_resolver_returns_false_when_any_element_fails_the_predicate()
{
var context = new GraphContext();
context.GraphVariables.DefineArrayVariable(
"numbers",
[new Variant128(3), new Variant128(1)]);

var resolver = new AllResolver(
new ArrayVariableResolver("numbers", typeof(int)),
new ComparisonResolver(
new ElementValueResolver(typeof(int)),
ComparisonOperation.GreaterThan,
new VariantResolver(new Variant128(1), typeof(int))));

resolver.Resolve(context).AsBool().Should().BeFalse();
}

[Fact]
[Trait("Resolver", "All")]
public void All_resolver_returns_true_for_empty_arrays()
{
var resolver = new AllResolver(
new ArrayVariableResolver("missing", typeof(int)),
new ComparisonResolver(
new ElementValueResolver(typeof(int)),
ComparisonOperation.GreaterThan,
new VariantResolver(new Variant128(1), typeof(int))));

resolver.Resolve(new GraphContext()).AsBool().Should().BeTrue();
}
}
77 changes: 77 additions & 0 deletions Forge.Tests/Statescript/Resolvers/AnyResolverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright © Gamesmiths Guild.

using FluentAssertions;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Cues;
using Gamesmiths.Forge.Statescript;
using Gamesmiths.Forge.Statescript.Properties;
using Gamesmiths.Forge.Tags;
using Gamesmiths.Forge.Tests.Helpers;

namespace Gamesmiths.Forge.Tests.Statescript.Resolvers;

public class AnyResolverTests(TagsAndCuesFixture tagsAndCuesFixture) : IClassFixture<TagsAndCuesFixture>
{
private readonly TagsManager _tagsManager = tagsAndCuesFixture.TagsManager;
private readonly CuesManager _cuesManager = tagsAndCuesFixture.CuesManager;

[Fact]
[Trait("Resolver", "Any")]
public void Any_resolver_returns_true_for_non_empty_arrays_without_a_predicate()
{
var context = new GraphContext();
context.GraphVariables.DefineArrayVariable("numbers", [new Variant128(3)]);

var resolver = new AnyResolver(new ArrayVariableResolver("numbers", typeof(int)));

resolver.Resolve(context).AsBool().Should().BeTrue();
}

[Fact]
[Trait("Resolver", "Any")]
public void Any_resolver_returns_false_for_empty_arrays()
{
var resolver = new AnyResolver(new ArrayVariableResolver("missing", typeof(int)));

resolver.Resolve(new GraphContext()).AsBool().Should().BeFalse();
}

[Fact]
[Trait("Resolver", "Any")]
public void Any_resolver_tests_entities_against_the_predicate()
{
var entity1 = new VitalTestEntity(_tagsManager, _cuesManager);
var entity2 = new VitalTestEntity(_tagsManager, _cuesManager);
entity1.VitalAttributeSet.UpdateBaseHealth(10);
entity2.VitalAttributeSet.UpdateBaseHealth(70);

var context = new GraphContext();
context.GraphVariables.DefineObjectArrayVariable<IForgeEntity>("targets", [entity1, entity2]);

var resolver = new AnyResolver(
new EntityArrayVariableResolver("targets"),
new ComparisonResolver(
new AttributeResolver("VitalAttributeSet.CurrentHealth", new ElementEntityResolver()),
ComparisonOperation.GreaterThan,
new VariantResolver(new Variant128(50), typeof(int))));

resolver.Resolve(context).AsBool().Should().BeTrue();
}

[Fact]
[Trait("Resolver", "Any")]
public void Any_resolver_returns_false_when_no_element_matches_the_predicate()
{
var context = new GraphContext();
context.GraphVariables.DefineArrayVariable("numbers", [new Variant128(1), new Variant128(2)]);

var resolver = new AnyResolver(
new ArrayVariableResolver("numbers", typeof(int)),
new ComparisonResolver(
new ElementValueResolver(typeof(int)),
ComparisonOperation.GreaterThan,
new VariantResolver(new Variant128(5), typeof(int))));

resolver.Resolve(context).AsBool().Should().BeFalse();
}
}
53 changes: 53 additions & 0 deletions Forge.Tests/Statescript/Resolvers/AppendResolverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright © Gamesmiths Guild.

using FluentAssertions;
using Gamesmiths.Forge.Statescript;
using Gamesmiths.Forge.Statescript.Properties;

namespace Gamesmiths.Forge.Tests.Statescript.Resolvers;

public class AppendResolverTests
{
[Fact]
[Trait("Resolver", "Append")]
public void Append_resolver_adds_elements_to_the_end()
{
var context = new GraphContext();
context.GraphVariables.DefineArrayVariable("numbers", [new Variant128(3)]);
var source = new ArrayVariableResolver("numbers", typeof(int));

var resolver = new AppendResolver(
source,
new VariantResolver(new Variant128(1), typeof(int)),
new VariantResolver(new Variant128(2), typeof(int)));

Variant128[] result = resolver.ResolveArray(context);

result.Should().HaveCount(3);
result[0].AsInt().Should().Be(3);
result[1].AsInt().Should().Be(1);
result[2].AsInt().Should().Be(2);
}

[Fact]
[Trait("Resolver", "Append")]
public void Append_resolver_rejects_element_resolvers_of_a_different_type()
{
var source = new ArrayVariableResolver("numbers", typeof(int));

Action act = () => _ = new AppendResolver(source, new VariantResolver(new Variant128(1f), typeof(float)));

act.Should().Throw<ArgumentException>();
}

[Fact]
[Trait("Resolver", "Append")]
public void Append_resolver_rejects_null_element_resolvers()
{
var source = new ArrayVariableResolver("numbers", typeof(int));

Action act = () => _ = new AppendResolver(source, (IPropertyResolver)null!);

act.Should().Throw<ArgumentException>();
}
}
96 changes: 96 additions & 0 deletions Forge.Tests/Statescript/Resolvers/ArrayPipelineIntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright © Gamesmiths Guild.

using FluentAssertions;
using Gamesmiths.Forge.Core;
using Gamesmiths.Forge.Cues;
using Gamesmiths.Forge.Statescript;
using Gamesmiths.Forge.Statescript.Properties;
using Gamesmiths.Forge.Tags;
using Gamesmiths.Forge.Tests.Helpers;

namespace Gamesmiths.Forge.Tests.Statescript.Resolvers;

/// <summary>
/// End-to-end coverage for the motivating array pipeline: collect entities, sort them by a per-element key, and keep
/// the closest three. The per-element key is authored purely from composable resolvers
/// (<see cref="ElementEntityResolver"/> feeding an <see cref="AttributeResolver"/>).
/// </summary>
/// <param name="tagsAndCuesFixture">The fixture providing tags and cues managers.</param>
public class ArrayPipelineIntegrationTests(TagsAndCuesFixture tagsAndCuesFixture) : IClassFixture<TagsAndCuesFixture>
{
private readonly TagsManager _tagsManager = tagsAndCuesFixture.TagsManager;
private readonly CuesManager _cuesManager = tagsAndCuesFixture.CuesManager;

[Fact]
[Trait("Resolver", "ArrayPipeline")]
public void Order_by_take_pipeline_selects_the_three_closest_entities()
{
// "Distance to the owner" is modeled as an attribute so the whole pipeline runs on core resolvers.
VitalTestEntity[] entities =
[
CreateEntityWithDistance(50),
CreateEntityWithDistance(10),
CreateEntityWithDistance(40),
CreateEntityWithDistance(20),
CreateEntityWithDistance(30),
];

var context = new GraphContext();
context.GraphVariables.DefineObjectArrayVariable<IForgeEntity>(
"nearbyEntities",
[entities[0], entities[1], entities[2], entities[3], entities[4]]);

var threeClosest = new ObjectTakeResolver<IForgeEntity>(
new ObjectOrderByResolver<IForgeEntity>(
new EntityArrayVariableResolver("nearbyEntities"),
new AttributeResolver("VitalAttributeSet.CurrentHealth", new ElementEntityResolver())),
new VariantResolver(new Variant128(3), typeof(int)));

IForgeEntity[] result = threeClosest.ResolveArray(context);

result.Should().Equal(entities[1], entities[3], entities[4]);
}

[Fact]
[Trait("Resolver", "ArrayPipeline")]
public void Where_order_by_take_pipeline_composes_filtering_and_sorting()
{
VitalTestEntity[] entities =
[
CreateEntityWithDistance(50),
CreateEntityWithDistance(10),
CreateEntityWithDistance(40),
CreateEntityWithDistance(20),
];

var context = new GraphContext();
context.GraphVariables.DefineObjectArrayVariable<IForgeEntity>(
"nearbyEntities",
[entities[0], entities[1], entities[2], entities[3]]);

var distanceKey = new AttributeResolver("VitalAttributeSet.CurrentHealth", new ElementEntityResolver());

// Keep entities farther than 15, then take the two closest of those.
var pipeline = new ObjectTakeResolver<IForgeEntity>(
new ObjectOrderByResolver<IForgeEntity>(
new ObjectWhereResolver<IForgeEntity>(
new EntityArrayVariableResolver("nearbyEntities"),
new ComparisonResolver(
distanceKey,
ComparisonOperation.GreaterThan,
new VariantResolver(new Variant128(15), typeof(int)))),
distanceKey),
new VariantResolver(new Variant128(2), typeof(int)));

IForgeEntity[] result = pipeline.ResolveArray(context);

result.Should().Equal(entities[3], entities[2]);
}

private VitalTestEntity CreateEntityWithDistance(int distance)
{
var entity = new VitalTestEntity(_tagsManager, _cuesManager);
entity.VitalAttributeSet.UpdateBaseHealth(distance);
return entity;
}
}
49 changes: 49 additions & 0 deletions Forge.Tests/Statescript/Resolvers/AverageResolverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright © Gamesmiths Guild.

using FluentAssertions;
using Gamesmiths.Forge.Statescript;
using Gamesmiths.Forge.Statescript.Properties;

namespace Gamesmiths.Forge.Tests.Statescript.Resolvers;

public class AverageResolverTests
{
[Fact]
[Trait("Resolver", "Average")]
public void Average_resolver_averages_int_elements_as_double()
{
var context = new GraphContext();
context.GraphVariables.DefineArrayVariable(
"numbers",
[new Variant128(3), new Variant128(1), new Variant128(2)]);

var resolver = new AverageResolver(new ArrayVariableResolver("numbers", typeof(int)));

resolver.ValueType.Should().Be(typeof(double));
resolver.Resolve(context).AsDouble().Should().Be(2d);
}

[Fact]
[Trait("Resolver", "Average")]
public void Average_resolver_averages_float_elements_as_float()
{
var context = new GraphContext();
context.GraphVariables.DefineArrayVariable(
"numbers",
[new Variant128(1f), new Variant128(2f)]);

var resolver = new AverageResolver(new ArrayVariableResolver("numbers", typeof(float)));

resolver.ValueType.Should().Be(typeof(float));
resolver.Resolve(context).AsFloat().Should().Be(1.5f);
}

[Fact]
[Trait("Resolver", "Average")]
public void Average_resolver_returns_zero_for_empty_arrays()
{
var resolver = new AverageResolver(new ArrayVariableResolver("missing", typeof(int)));

resolver.Resolve(new GraphContext()).AsDouble().Should().Be(0d);
}
}
41 changes: 41 additions & 0 deletions Forge.Tests/Statescript/Resolvers/ConcatResolverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright © Gamesmiths Guild.

using FluentAssertions;
using Gamesmiths.Forge.Statescript;
using Gamesmiths.Forge.Statescript.Properties;

namespace Gamesmiths.Forge.Tests.Statescript.Resolvers;

public class ConcatResolverTests
{
[Fact]
[Trait("Resolver", "Concat")]
public void Concat_resolver_appends_the_second_array_after_the_first()
{
var context = new GraphContext();
context.GraphVariables.DefineArrayVariable("first", [new Variant128(3), new Variant128(1)]);
context.GraphVariables.DefineArrayVariable("second", [new Variant128(2)]);

var resolver = new ConcatResolver(
new ArrayVariableResolver("first", typeof(int)),
new ArrayVariableResolver("second", typeof(int)));

Variant128[] result = resolver.ResolveArray(context);

result.Should().HaveCount(3);
result[0].AsInt().Should().Be(3);
result[1].AsInt().Should().Be(1);
result[2].AsInt().Should().Be(2);
}

[Fact]
[Trait("Resolver", "Concat")]
public void Concat_resolver_rejects_mismatched_element_types()
{
Action act = () => _ = new ConcatResolver(
new ArrayVariableResolver("first", typeof(int)),
new ArrayVariableResolver("second", typeof(float)));

act.Should().Throw<ArgumentException>();
}
}
Loading
Loading