📄 test/JsonLdRecipeParser.Tests/AmountTests/TryParseShould.cs
using System;
using System.Globalization;
using System.Linq;
using FsCheck;
using FsCheck.Fluent;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;

namespace JsonLdRecipeParser.Tests.AmountTests;

[TestClass]
public class TryParseShould
{
    [TestMethod]
    public void ReturnFalseForEmptyStrings()
    {
        // Arrange
        var text = "";

        // Act
        var success = Amount.TryParse(text, out _);

        // Assert
        success.ShouldBeFalse();
    }

    [TestMethod]
    [DataRow("  ")]
    [DataRow("\t")]
    public void ReturnFalseForWhiteSpaceStrings(string input)
    {
        // Arrange
        var text = input;

        // Act
        var success = Amount.TryParse(text, out _);

        // Assert
        success.ShouldBeFalse();
    }

    [TestMethod]
    public void ReturnUnitlessAmountForIntegerString()
    {
        // Arrange
        var text = "100";

        // Act
        var success = Amount.TryParse(text, out var amount);

        // Assert
        success.ShouldBeTrue();
        amount.ShouldNotBeNull();
        amount.Value.ShouldBe(new ExactAmountValue(100.0d));
        amount.Unit.ShouldBeNull();
    }

    [TestMethod]
    public void ReturnUnitlessAmountForRealStringWithPeriodDecimalSeparator()
    {
        // Arrange
        var text = "2.5";

        // Act
        var success = Amount.TryParse(text, out var amount);

        // Assert
        success.ShouldBeTrue();
        amount.ShouldNotBeNull();
        amount.Value.ShouldBe(new ExactAmountValue(2.5d));
        amount.Unit.ShouldBeNull();
    }

    [TestMethod]
    public void ReturnUnitlessAmountForRealStringWithCommaDecimalSeparator()
    {
        // Arrange
        var text = "2,5";

        // Act
        var success = Amount.TryParse(text, out var amount);

        // Assert
        success.ShouldBeTrue();
        amount.ShouldNotBeNull();
        amount.Value.ShouldBe(new ExactAmountValue(2.5d));
        amount.Unit.ShouldBeNull();
    }

    [TestMethod]
    public void ReturnUnitlessAmountForIntegerStringWithTrailingWhitespace()
    {
        // Arrange
        var text = "100   ";

        // Act
        var success = Amount.TryParse(text, out var amount);

        // Assert
        success.ShouldBeTrue();
        amount.ShouldNotBeNull();
        amount.Value.ShouldBe(new ExactAmountValue(100.0d));
        amount.Unit.ShouldBeNull();
    }

    [TestMethod]
    public void ReturnUnitlessAmountForAnyRealStringAndAnyCulture() =>
        Prop.ForAll(
                Gen.Choose(0, 1_000_000_000).Select(i => double.Round(i / 1000.0d, 3)).ToArbitrary(),
                Gen.Elements(CultureInfo.GetCultures(CultureTypes.AllCultures)).ToArbitrary(),
                (number, cultureInfo) =>
                {
                    // Arrange
                    var text = number.ToString(cultureInfo);

                    // Act
                    var success = Amount.TryParse(text, out var amount);

                    // Assert
                    success.ShouldBeTrue();
                    amount.ShouldNotBeNull();
                    var exactAmountValue = amount.Value.ShouldBeOfType<ExactAmountValue>();
                    exactAmountValue.Value.ShouldBe(number);
                    amount.Unit.ShouldBeNull();
                }
            )
            .QuickCheckThrowOnFailure();

    [TestMethod]
    public void ReturnUnitlessRangeAmountForIntegerRange()
    {
        // Arrange
        var text = "100-150";

        // Act
        var success = Amount.TryParse(text, out var amount);

        // Assert
        success.ShouldBeTrue();
        amount.ShouldNotBeNull();
        var range = amount.Value.ShouldBeOfType<RangeAmountValue>();
        range.From.ShouldBe(100.0d);
        range.To.ShouldBe(150.0d);
    }

    [TestMethod]
    public void ReturnUnitlessRangeAmountForDecimalRange()
    {
        // Arrange
        var text = "2,5-4.5";

        // Act
        var success = Amount.TryParse(text, out var amount);

        // Assert
        success.ShouldBeTrue();
        amount.ShouldNotBeNull();
        var range = amount.Value.ShouldBeOfType<RangeAmountValue>();
        range.From.ShouldBe(2.5d);
        range.To.ShouldBe(4.5d);
    }

    [TestMethod]
    public void ReturnUnitWithWhitespace()
    {
        // Arrange
        var text = "10 dl";

        // Act
        var success = Amount.TryParse(text, out var amount);

        // Assert
        success.ShouldBeTrue();
        amount.ShouldNotBeNull();
        amount.Value.ShouldBe(new ExactAmountValue(10.0d));
        amount.Unit.ShouldBe("dl");
    }

    [TestMethod]
    public void ReturnUnitWithoutWhitespace()
    {
        // Arrange
        var text = "250g";

        // Act
        var success = Amount.TryParse(text, out var amount);

        // Assert
        success.ShouldBeTrue();
        amount.ShouldNotBeNull();
        amount.Value.ShouldBe(new ExactAmountValue(250.0d));
        amount.Unit.ShouldBe("g");
    }

    [TestMethod]
    public void ReturnUnitWithTrailingWhitespace()
    {
        // Arrange
        var text = "250g  ";

        // Act
        var success = Amount.TryParse(text, out var amount);

        // Assert
        success.ShouldBeTrue();
        amount.ShouldNotBeNull();
        amount.Value.ShouldBe(new ExactAmountValue(250.0d));
        amount.Unit.ShouldBe("g");
    }
}