📄 ui/tests/PhysicsLayoutTests/ConstructShould.cs
using System;
using Avalonia;
using Cfa.Ui.Graph;
using Shouldly;
using Xunit;

namespace Cfa.Ui.Tests.PhysicsLayoutTests;

public sealed class ConstructShould
{
    [Fact]
    public void RejectMissingOrNonfiniteInitialCoordinates()
    {
        // Arrange
        PhysicsNode[] nodes = [new(-1, 100), new(0, 100)];
        Point[][] starts =
        [
            [default],
            [default, new Point(double.NaN, 0)],
        ];

        foreach (var start in starts)
        {
            // Act
            var construct = () => new PhysicsLayout(nodes, initialPositions: start);

            // Assert
            construct.ShouldThrow<ArgumentException>();
        }
    }

    [Fact]
    public void NormalizeTheSuppliedOriginToZeroWithoutChangingRelativePositions()
    {
        // Arrange
        Point[] starts = [new(50, 70), new(150, 100)];

        // Act
        var layout = new PhysicsLayout([new(-1, 100), new(0, 100)], initialPositions: starts);

        // Assert
        layout.Positions[0].ShouldBe(default(Point));
        layout.Positions[1].ShouldBe(new Point(100, 30));
        starts[0].ShouldBe(new Point(50, 70));
    }

    [Theory]
    [InlineData(0)]
    [InlineData(-1)]
    [InlineData(double.NaN)]
    [InlineData(double.PositiveInfinity)]
    public void RejectInvalidRadii(double radius)
    {
        // Arrange
        PhysicsNode[] nodes = [new(-1, radius)];

        // Act
        var construct = () => new PhysicsLayout(nodes);

        // Assert
        construct.ShouldThrow<ArgumentException>();
    }

    [Theory]
    [InlineData(-1)]
    [InlineData(1)]
    [InlineData(5)]
    public void RejectMissingParentsAndCycles(int parent)
    {
        // Arrange
        PhysicsNode[] nodes = [new(-1, 100), new(parent, 100)];

        // Act
        var construct = () => new PhysicsLayout(nodes);

        // Assert
        construct.ShouldThrow<ArgumentException>();
    }

    [Fact]
    public void RejectInvalidSimulationSettings()
    {
        // Arrange
        PhysicsOptions[] settings =
        [
            new() { MaxIterations = 0 },
            new() { Damping = 1 },
            new() { Damping = -1 },
            new() { MaxSpeed = 0 },
            new() { SpringStrength = double.NaN },
            new() { Tolerance = 0 },
            new() { EdgeCrossingStrength = -1 },
            new() { EdgeNodeStrength = double.NaN },
            new() { EdgeClearance = 0 },
        ];

        foreach (var setting in settings)
        {
            // Act
            var construct = () => new PhysicsLayout([], setting);

            // Assert
            construct.ShouldThrow<ArgumentOutOfRangeException>();
        }
    }
}