diff --git a/src/Nox.Types.EntityFramework/Types/Length/LengthConverter.cs b/src/Nox.Types.EntityFramework/Types/Length/LengthConverter.cs new file mode 100644 index 0000000..a99295b --- /dev/null +++ b/src/Nox.Types.EntityFramework/Types/Length/LengthConverter.cs @@ -0,0 +1,12 @@ +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Nox.Types.EntityFramework.Types; + +public class LengthToFootConverter : ValueConverter +{ + public LengthToFootConverter() : base(length => (double)length.ToFeet(), lengthValue => Length.FromFeet(lengthValue)) { } +} +public class LengthToMeterConverter : ValueConverter +{ + public LengthToMeterConverter() : base(length => (double)length.ToMeters(), lengthValue => Length.FromMeters(lengthValue)) { } +} diff --git a/src/Nox.Types/Common/MeasurementConversionFactor.cs b/src/Nox.Types/Common/MeasurementConversionFactor.cs new file mode 100644 index 0000000..43fe3c5 --- /dev/null +++ b/src/Nox.Types/Common/MeasurementConversionFactor.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace Nox.Common; + +internal sealed class MeasurementConversionFactor +{ + private static readonly Dictionary<(MeasurementTypeUnit, MeasurementTypeUnit), double> DefinedConversionFactors = new() + { + { (MeasurementTypeUnit.Foot, MeasurementTypeUnit.Meter), 0.30480000033}, + { (MeasurementTypeUnit.Meter, MeasurementTypeUnit.Foot), 3.28083989142}, + { (MeasurementTypeUnit.Kilometer, MeasurementTypeUnit.Mile), 0.62137119102}, + { (MeasurementTypeUnit.Mile, MeasurementTypeUnit.Kilometer), 1.60934400315}, + { (MeasurementTypeUnit.SquareFoot, MeasurementTypeUnit.SquareMeter), 0.09290304}, + { (MeasurementTypeUnit.SquareMeter, MeasurementTypeUnit.SquareFoot), 10.76391042}, + }; + + public double Value { get; } + + public MeasurementConversionFactor(MeasurementTypeUnit sourceUnit, MeasurementTypeUnit targetUnit) + { + Value = ResolveConversionFactor(sourceUnit, targetUnit); + } + + private static double ResolveConversionFactor(MeasurementTypeUnit sourceUnit, MeasurementTypeUnit targetUnit) + { + var conversion = (sourceUnit, targetUnit); + + if (DefinedConversionFactors.ContainsKey(conversion)) + return DefinedConversionFactors[conversion]; + + else if (sourceUnit == targetUnit) + return 1; + + throw new NotImplementedException($"No conversion defined from {sourceUnit} to {targetUnit}."); + } +} diff --git a/src/Nox.Types/Common/MeasurementTypeUnit.cs b/src/Nox.Types/Common/MeasurementTypeUnit.cs new file mode 100644 index 0000000..ebdc93b --- /dev/null +++ b/src/Nox.Types/Common/MeasurementTypeUnit.cs @@ -0,0 +1,14 @@ +namespace Nox.Common; + +internal enum MeasurementTypeUnit +{ + // Length + Foot = 1, + Meter = 2, + Kilometer = 3, + Mile = 4, + + // Area + SquareFoot = 5, + SquareMeter = 6, +} diff --git a/src/Nox.Types/Nox.Types.csproj b/src/Nox.Types/Nox.Types.csproj index 1075134..cc2f7be 100644 --- a/src/Nox.Types/Nox.Types.csproj +++ b/src/Nox.Types/Nox.Types.csproj @@ -24,5 +24,6 @@ + diff --git a/src/Nox.Types/Types/Area/Area.cs b/src/Nox.Types/Types/Area/Area.cs index beeb01e..883bc76 100644 --- a/src/Nox.Types/Types/Area/Area.cs +++ b/src/Nox.Types/Types/Area/Area.cs @@ -1,5 +1,7 @@ -using System; +using Nox.Common; +using System; using System.Collections.Generic; +using System.Globalization; namespace Nox.Types; @@ -91,7 +93,16 @@ internal override ValidationResult Validate() return result; } - public override string ToString() => $"{Value:G} {Unit.ToSymbol()}"; + public override string ToString() + => $"{Value.ToString($"0.{new string('#', QuantityValueDecimalPrecision)}", CultureInfo.InvariantCulture)} {Unit.ToSymbol()}"; + + /// + /// Returns a string representation of the object using the specified . + /// + /// The format provider for the length value. + /// A string representation of the object with the value formatted using the specified . + public string ToString(IFormatProvider formatProvider) + => $"{Value.ToString(formatProvider)} {Unit.ToSymbol()}"; protected override IEnumerable> GetEqualityComponents() { @@ -104,18 +115,10 @@ protected override IEnumerable> GetEqualityComponen private QuantityValue? _squareFeet; public QuantityValue ToSquareFeet() => (_squareFeet ??= GetAreaIn(AreaTypeUnit.SquareFoot)); - private QuantityValue GetAreaIn(AreaTypeUnit unit) + private QuantityValue GetAreaIn(AreaTypeUnit targetUnit) { - if (Unit == unit) - return Round(Value); - - else if (Unit == AreaTypeUnit.SquareMeter && unit == AreaTypeUnit.SquareFoot) - return Round(Value * 10.76391042); - - else if (Unit == AreaTypeUnit.SquareFoot && unit == AreaTypeUnit.SquareMeter) - return Round(Value * 0.09290304); - - throw new NotImplementedException($"No conversion defined from {Unit} to {unit}."); + var factor = new MeasurementConversionFactor((MeasurementTypeUnit)Unit, (MeasurementTypeUnit)targetUnit).Value; + return Round(Value * factor); } private static QuantityValue Round(QuantityValue value) diff --git a/src/Nox.Types/Types/Area/AreaTypeUnit.cs b/src/Nox.Types/Types/Area/AreaTypeUnit.cs index ad55f08..55b1f04 100644 --- a/src/Nox.Types/Types/Area/AreaTypeUnit.cs +++ b/src/Nox.Types/Types/Area/AreaTypeUnit.cs @@ -4,8 +4,8 @@ namespace Nox.Types; public enum AreaTypeUnit { - SquareFoot, - SquareMeter, + SquareFoot = 5, + SquareMeter = 6, } public static class AreaTypeUnitExtensions diff --git a/src/Nox.Types/Types/Distance/Distance.cs b/src/Nox.Types/Types/Distance/Distance.cs index 050810b..57a6580 100644 --- a/src/Nox.Types/Types/Distance/Distance.cs +++ b/src/Nox.Types/Types/Distance/Distance.cs @@ -1,5 +1,7 @@ -using System; +using Nox.Common; +using System; using System.Collections.Generic; +using System.Globalization; namespace Nox.Types; @@ -112,7 +114,16 @@ protected override IEnumerable> GetEqualityComponen yield return new KeyValuePair(nameof(Value), ToKilometers()); } - public override string ToString() => $"{Value:G} {Unit.ToSymbol()}"; + public override string ToString() + => $"{Value.ToString($"0.{new string('#', QuantityValueDecimalPrecision)}", CultureInfo.InvariantCulture)} {Unit.ToSymbol()}"; + + /// + /// Returns a string representation of the object using the specified . + /// + /// The format provider for the length value. + /// A string representation of the object with the value formatted using the specified . + public string ToString(IFormatProvider formatProvider) + => $"{Value.ToString(formatProvider)} {Unit.ToSymbol()}"; private QuantityValue? _kilometers; @@ -122,18 +133,10 @@ protected override IEnumerable> GetEqualityComponen public QuantityValue ToMiles() => (_miles ??= GetDistanceIn(DistanceTypeUnit.Mile)); - private QuantityValue GetDistanceIn(DistanceTypeUnit unit) + private QuantityValue GetDistanceIn(DistanceTypeUnit targetUnit) { - if (Unit == unit) - return Round(Value); - - else if (Unit == DistanceTypeUnit.Kilometer && unit == DistanceTypeUnit.Mile) - return Round(Value * 0.62137119102); - - else if (Unit == DistanceTypeUnit.Mile && unit == DistanceTypeUnit.Kilometer) - return Round(Value * 1.60934400315); - - throw new NotImplementedException($"No conversion defined from {Unit} to {unit}."); + var factor = new MeasurementConversionFactor((MeasurementTypeUnit)Unit, (MeasurementTypeUnit)targetUnit).Value; + return Round(Value * factor); } private static QuantityValue Round(QuantityValue value) diff --git a/src/Nox.Types/Types/Distance/DistanceTypeUnit.cs b/src/Nox.Types/Types/Distance/DistanceTypeUnit.cs index 007028e..b3b16e3 100644 --- a/src/Nox.Types/Types/Distance/DistanceTypeUnit.cs +++ b/src/Nox.Types/Types/Distance/DistanceTypeUnit.cs @@ -4,8 +4,8 @@ namespace Nox.Types; public enum DistanceTypeUnit { - Kilometer, - Mile + Kilometer = 3, + Mile = 4, } public static class DistanceTypeUnitExtensions diff --git a/src/Nox.Types/Types/Length/Length.cs b/src/Nox.Types/Types/Length/Length.cs index c96d70d..6e1307e 100644 --- a/src/Nox.Types/Types/Length/Length.cs +++ b/src/Nox.Types/Types/Length/Length.cs @@ -1,9 +1,126 @@ +using Nox.Common; +using System; +using System.Collections.Generic; +using System.Globalization; + namespace Nox.Types; +/// +/// Represents a Nox type and value object. +/// +public sealed class Length : ValueObject +{ + private const int QuantityValueDecimalPrecision = 6; + + public LengthTypeUnit Unit { get; private set; } = LengthTypeUnit.Meter; + + public Length() { Value = 0; } + + /// + /// Creates a new instance of object in meters. + /// + /// The value to create the with + /// + /// + public static Length FromMeters(QuantityValue value) + => From(value, LengthTypeUnit.Meter); + + /// + /// Creates a new instance of object in feet. + /// + /// The origin value to create the with + /// + /// + public static Length FromFeet(QuantityValue value) + => From(value, LengthTypeUnit.Foot); + /// - /// Represents a Nox type and value object. + /// Creates a new instance of object in meters. /// - /// Placeholder, needs to be implemented - public sealed class Length : ValueObject + /// The value to create the with + /// + /// + public new static Length From(QuantityValue value) + => From(value, LengthTypeUnit.Meter); + + /// + /// Creates a new instance of object with the specified . + /// + /// The value to create the with + /// The to create the with + /// + /// + public static Length From(QuantityValue value, LengthTypeUnit unit) { + var newObject = new Length + { + Value = Round(value), + Unit = unit, + }; + + var validationResult = newObject.Validate(); + + if (!validationResult.IsValid) + { + throw new TypeValidationException(validationResult.Errors); + } + + return newObject; } + + /// + /// Validates a object. + /// + /// true if the value is valid. + internal override ValidationResult Validate() + { + var result = Value.Validate(); + + if (Value < 0 && !double.IsNaN((double)Value) && !double.IsInfinity((double)Value)) + { + result.Errors.Add(new ValidationFailure(nameof(Value), $"Could not create a Nox Length type as negative length value {Value} is not allowed.")); + } + + if (!Enum.IsDefined(typeof(LengthTypeUnit), Unit)) + { + result.Errors.Add(new ValidationFailure(nameof(Unit), $"Could not create a Nox Length type as unit {Unit} is not supported.")); + } + + return result; + } + + protected override IEnumerable> GetEqualityComponents() + { + yield return new KeyValuePair(nameof(Value), ToMeters()); + } + + public override string ToString() + => $"{Value.ToString($"0.{new string('#', QuantityValueDecimalPrecision)}", CultureInfo.InvariantCulture)} {Unit.ToSymbol()}"; + + /// + /// Returns a string representation of the object using the specified . + /// + /// The format provider for the length value. + /// A string representation of the object with the value formatted using the specified . + public string ToString(IFormatProvider formatProvider) + => $"{Value.ToString(formatProvider)} {Unit.ToSymbol()}"; + + private QuantityValue? _meters; + + public QuantityValue ToMeters() + => (_meters ??= GetLengthIn(LengthTypeUnit.Meter)); + + private QuantityValue? _feet; + + public QuantityValue ToFeet() + => (_feet ??= GetLengthIn(LengthTypeUnit.Foot)); + + private QuantityValue GetLengthIn(LengthTypeUnit targetUnit) + { + var factor = new MeasurementConversionFactor((MeasurementTypeUnit)Unit, (MeasurementTypeUnit)targetUnit).Value; + return Round(Value * factor); + } + + private static QuantityValue Round(QuantityValue value) + => Math.Round((double)value, QuantityValueDecimalPrecision); +} \ No newline at end of file diff --git a/src/Nox.Types/Types/Length/LengthTypeUnit.cs b/src/Nox.Types/Types/Length/LengthTypeUnit.cs new file mode 100644 index 0000000..1f8bc04 --- /dev/null +++ b/src/Nox.Types/Types/Length/LengthTypeUnit.cs @@ -0,0 +1,22 @@ +using System; + +namespace Nox.Types; + +public enum LengthTypeUnit +{ + Foot = 1, + Meter = 2, +} + +public static class LengthTypeUnitExtensions +{ + public static string ToSymbol(this LengthTypeUnit unit) + { + return unit switch + { + LengthTypeUnit.Foot => "ft", + LengthTypeUnit.Meter => "m", + _ => throw new NotImplementedException($"No symbol defined for unit {unit}.") + }; + } +} \ No newline at end of file diff --git a/tests/Nox.Types.Tests/Common/MeasurementConversionFactorTests.cs b/tests/Nox.Types.Tests/Common/MeasurementConversionFactorTests.cs new file mode 100644 index 0000000..fa48e1f --- /dev/null +++ b/tests/Nox.Types.Tests/Common/MeasurementConversionFactorTests.cs @@ -0,0 +1,108 @@ +using FluentAssertions; +using Nox.Common; + +namespace Nox.Types.Tests.Common; + +public class MeasurementConversionFactorTests +{ + [Fact] + public void MeasurementUnitConverter_GetConversionFactor_FromFootToMeter_ReturnsValue() + { + var factor = new MeasurementConversionFactor(MeasurementTypeUnit.Foot, MeasurementTypeUnit.Meter); + + factor.Value.Should().Be(0.30480000033); + } + + [Fact] + public void MeasurementUnitConverter_GetConversionFactor_FromMeterToFoot_ReturnsValue() + { + var factor = new MeasurementConversionFactor(MeasurementTypeUnit.Meter, MeasurementTypeUnit.Foot); + + factor.Value.Should().Be(3.28083989142); + } + + [Fact] + public void MeasurementUnitConverter_GetConversionFactor_FromKilometerToMile_ReturnsValue() + { + var factor = new MeasurementConversionFactor(MeasurementTypeUnit.Kilometer, MeasurementTypeUnit.Mile); + + factor.Value.Should().Be(0.62137119102); + } + + [Fact] + public void MeasurementUnitConverter_GetConversionFactor_FromMileToKilometer_ReturnsValue() + { + var factor = new MeasurementConversionFactor(MeasurementTypeUnit.Mile, MeasurementTypeUnit.Kilometer); + + factor.Value.Should().Be(1.60934400315); + } + + [Fact] + public void MeasurementUnitConverter_GetConversionFactor_FromSquareFootToSquareMeter_ReturnsValue() + { + var factor = new MeasurementConversionFactor(MeasurementTypeUnit.SquareFoot, MeasurementTypeUnit.SquareMeter); + + factor.Value.Should().Be(0.09290304); + } + + [Fact] + public void MeasurementUnitConverter_GetConversionFactor_FromSquareMeterToSquareFoot_ReturnsValue() + { + var factor = new MeasurementConversionFactor(MeasurementTypeUnit.SquareMeter, MeasurementTypeUnit.SquareFoot); + + factor.Value.Should().Be(10.76391042); + } + + [Fact] + public void MeasurementUnitConverter_GetConversionFactor_WithSameSourceAndTargetUnit_ReturnsValue() + { + var factor = new MeasurementConversionFactor(MeasurementTypeUnit.Foot, MeasurementTypeUnit.Foot); + + factor.Value.Should().Be(1); + } + + [Fact] + public void MeasurementUnitConverter_GetConversionFactor_WithUnsupportedConversion_ThrowsException() + { + var action = () => new MeasurementConversionFactor(MeasurementTypeUnit.SquareMeter, MeasurementTypeUnit.Meter); + + action.Should().Throw() + .WithMessage("No conversion defined from SquareMeter to Meter."); + } + + [Fact] + public void MeasurementUnitType_Foot_ReturnsSameValueAsLengthTypeUnit() + { + ((int)MeasurementTypeUnit.Foot).Should().Be((int)LengthTypeUnit.Foot); + } + + [Fact] + public void MeasurementUnitType_Meter_ReturnsSameValueAsLengthTypeUnit() + { + ((int)MeasurementTypeUnit.Meter).Should().Be((int)LengthTypeUnit.Meter); + } + + [Fact] + public void MeasurementUnitType_Kilometer_ReturnsSameValueAsDistanceTypeUnit() + { + ((int)MeasurementTypeUnit.Kilometer).Should().Be((int)DistanceTypeUnit.Kilometer); + } + + [Fact] + public void MeasurementUnitType_Mile_ReturnsSameValueAsDistanceTypeUnit() + { + ((int)MeasurementTypeUnit.Mile).Should().Be((int)DistanceTypeUnit.Mile); + } + + [Fact] + public void MeasurementUnitType_SquareFoot_ReturnsSameValueAsLengthTypeUnit() + { + ((int)MeasurementTypeUnit.SquareFoot).Should().Be((int)AreaTypeUnit.SquareFoot); + } + + [Fact] + public void MeasurementUnitType_SquareMeter_ReturnsSameValueAsLengthTypeUnit() + { + ((int)MeasurementTypeUnit.SquareMeter).Should().Be((int)AreaTypeUnit.SquareMeter); + } +} diff --git a/tests/Nox.Types.Tests/EntityFrameworkTests/Configuration/CountryDbConfiguration.cs b/tests/Nox.Types.Tests/EntityFrameworkTests/Configuration/CountryDbConfiguration.cs index 2691ad3..c91dba9 100644 --- a/tests/Nox.Types.Tests/EntityFrameworkTests/Configuration/CountryDbConfiguration.cs +++ b/tests/Nox.Types.Tests/EntityFrameworkTests/Configuration/CountryDbConfiguration.cs @@ -23,6 +23,7 @@ public void Configure(EntityTypeBuilder builder) builder.Property(e => e.InternetDomain).HasConversion(); builder.Property(e => e.CountryCode3).HasConversion(); builder.Property(e => e.IPAddress).HasConversion(); + builder.Property(e => e.LongestHikingTrailInMeters).HasConversion(); // Configure Multi-value ValueObjects builder.OwnsOne(e => e.LatLong).Ignore(p => p.Value); diff --git a/tests/Nox.Types.Tests/EntityFrameworkTests/Models/Country.cs b/tests/Nox.Types.Tests/EntityFrameworkTests/Models/Country.cs index b02ced5..63efd5f 100644 --- a/tests/Nox.Types.Tests/EntityFrameworkTests/Models/Country.cs +++ b/tests/Nox.Types.Tests/EntityFrameworkTests/Models/Country.cs @@ -77,4 +77,9 @@ public sealed class Country /// Gets or sets the IP Address. /// public IpAddress IPAddress { get; set; } = null!; + + /// + /// Gets or sets the longest hiking trail in meters. + /// + public Length LongestHikingTrailInMeters { get; set; } = null!; } diff --git a/tests/Nox.Types.Tests/EntityFrameworkTests/NoxTypesEntityFrameworkTests.cs b/tests/Nox.Types.Tests/EntityFrameworkTests/NoxTypesEntityFrameworkTests.cs index b51ab1c..5b1a538 100644 --- a/tests/Nox.Types.Tests/EntityFrameworkTests/NoxTypesEntityFrameworkTests.cs +++ b/tests/Nox.Types.Tests/EntityFrameworkTests/NoxTypesEntityFrameworkTests.cs @@ -39,6 +39,7 @@ public void Countries_CanRead_LatLong() CountryCode3 = CountryCode3.From("CHE"), IPAddress = IpAddress.From("102.129.143.255"), DateTimeRange = DateTimeRange.From(new DateTime(2023, 01, 01), new DateTime(2023, 02, 01)), + LongestHikingTrailInMeters = Length.From(390_000), }; DbContext.Countries.Add(newItem); DbContext.SaveChanges(); @@ -70,6 +71,7 @@ public void AddedItemShouldGetGeneratedId() InternetDomain = InternetDomain.From("admin.ch"), CountryCode3 = CountryCode3.From("CHE"), IPAddress = IpAddress.From("102.129.143.255"), + LongestHikingTrailInMeters = Length.From(390_000), }; DbContext.Countries.Add(newItem); DbContext.SaveChanges(); @@ -102,5 +104,7 @@ public void AddedItemShouldGetGeneratedId() Assert.Equal("102.129.143.255", item.IPAddress.Value); Assert.Equal(new DateTime(2023, 01, 01), item.DateTimeRange.Start); Assert.Equal(new DateTime(2023, 02, 01), item.DateTimeRange.End); + Assert.Equal(390_000, item.LongestHikingTrailInMeters.Value); + Assert.Equal(LengthTypeUnit.Meter, item.LongestHikingTrailInMeters.Unit); } } diff --git a/tests/Nox.Types.Tests/Types/Area/AreaTests.cs b/tests/Nox.Types.Tests/Types/Area/AreaTests.cs index 7ef17e3..09e13a7 100644 --- a/tests/Nox.Types.Tests/Types/Area/AreaTests.cs +++ b/tests/Nox.Types.Tests/Types/Area/AreaTests.cs @@ -1,3 +1,5 @@ +using System.Globalization; + namespace Nox.Types.Tests.Types; public class AreaTests @@ -145,30 +147,60 @@ public void Area_ToSquareFeet_ReturnsValue() Assert.Equal(134.54888, area.ToSquareFeet()); } - [Fact] - public void Area_ValueInSquareMeters_ToString_ReturnsString() + [Theory] + [InlineData("en-US")] + [InlineData("pt-PT")] + public void Area_ValueInSquareMeters_ToString_IsCultureIndepdendent(string culture) { void Test() { var area = Area.FromSquareMeters(12.5); - Assert.Equal("12.5 m²", area.ToString()); } - TestUtility.RunInInvariantCulture(Test); + TestUtility.RunInCulture(Test, culture); } - [Fact] - public void Area_ValueInSquareFeet_ToString_ReturnsString() + [Theory] + [InlineData("en-US", "12.5 m²")] + [InlineData("pt-PT", "12,5 m²")] + public void Area_ValueInSquareMeters_ToString_IsCultureDependent(string culture, string expected) { void Test() { - var area = Area.FromSquareFeet(134.548880); + var area = Area.FromSquareMeters(12.5); + Assert.Equal(expected, area.ToString(new CultureInfo(culture))); + } + + TestUtility.RunInCulture(Test, culture); + } + [Theory] + [InlineData("en-US")] + [InlineData("pt-PT")] + public void Area_ValueInSquareFeet_ToString_IsCultureIndependent(string culture) + { + void Test() + { + var area = Area.FromSquareFeet(134.548880); Assert.Equal("134.54888 ft²", area.ToString()); } - TestUtility.RunInInvariantCulture(Test); + TestUtility.RunInCulture(Test, culture); + } + + [Theory] + [InlineData("en-US", "134.54888 ft²")] + [InlineData("pt-PT", "134,54888 ft²")] + public void Area_ValueInSquareFeet_ToString_IsCultureDependent(string culture, string expected) + { + void Test() + { + var area = Area.FromSquareFeet(134.548880); + Assert.Equal(expected, area.ToString(new CultureInfo(culture))); + } + + TestUtility.RunInCulture(Test, culture); } [Fact] diff --git a/tests/Nox.Types.Tests/Types/Distance/DistanceTests.cs b/tests/Nox.Types.Tests/Types/Distance/DistanceTests.cs index 537a1df..58e92d1 100644 --- a/tests/Nox.Types.Tests/Types/Distance/DistanceTests.cs +++ b/tests/Nox.Types.Tests/Types/Distance/DistanceTests.cs @@ -1,4 +1,6 @@ -namespace Nox.Types.Tests.Types; +using System.Globalization; + +namespace Nox.Types.Tests.Types; public class DistanceTests { @@ -128,8 +130,10 @@ public void Distance_ToMiles_ReturnsValueInMiles() Assert.Equal(195.209352, distance.ToMiles()); } - [Fact] - public void Distance_ValueInKilometers_ToString_ReturnsValueAndUnit() + [Theory] + [InlineData("en-US")] + [InlineData("pt-PT")] + public void Distance_ValueInKilometers_ToString_IsCultureIndepdendent(string culture) { void Test() { @@ -137,11 +141,27 @@ void Test() Assert.Equal("314.159 km", distance.ToString()); } - TestUtility.RunInInvariantCulture(Test); + TestUtility.RunInCulture(Test, culture); } - [Fact] - public void Distance_ValueInMiles_ToString_ReturnsValueAndUnit() + [Theory] + [InlineData("en-US", "314.159 km")] + [InlineData("pt-PT", "314,159 km")] + public void Distance_ValueInKilometers_ToString_IsCultureDependent(string culture, string expected) + { + void Test() + { + var distance = Distance.FromKilometers(314.159); + Assert.Equal(expected, distance.ToString(new CultureInfo(culture))); + } + + TestUtility.RunInCulture(Test, culture); + } + + [Theory] + [InlineData("en-US")] + [InlineData("pt-PT")] + public void Distance_ValueInMiles_ToString_IsCultureIndependent(string culture) { void Test() { @@ -149,7 +169,21 @@ void Test() Assert.Equal("195.209 mi", distance.ToString()); } - TestUtility.RunInInvariantCulture(Test); + TestUtility.RunInCulture(Test, culture); + } + + [Theory] + [InlineData("en-US", "195.209 mi")] + [InlineData("pt-PT", "195,209 mi")] + public void Distance_ValueInMiles_ToString_IsCultureDependent(string culture, string expected) + { + void Test() + { + var distance = Distance.FromMiles(195.209); + Assert.Equal(expected, distance.ToString(new CultureInfo(culture))); + } + + TestUtility.RunInCulture(Test, culture); } [Fact] diff --git a/tests/Nox.Types.Tests/Types/Length/LengthTests.cs b/tests/Nox.Types.Tests/Types/Length/LengthTests.cs index cfd3de0..e5ca74d 100644 --- a/tests/Nox.Types.Tests/Types/Length/LengthTests.cs +++ b/tests/Nox.Types.Tests/Types/Length/LengthTests.cs @@ -1,11 +1,228 @@ // ReSharper disable once CheckNamespace +using FluentAssertions; +using System.Globalization; + namespace Nox.Types.Tests.Types; public class LengthTests { [Fact] - public void When_Create_Should() + public void Length_Constructor_ReturnsSameValueAndDefaultUnit() + { + var length = Length.From(95.755663); + + length.Value.Should().Be(95.755663); + length.Unit.Should().Be(LengthTypeUnit.Meter); + } + + [Fact] + public void Length_Constructor_WithUnit_ReturnsSameValueAndUnit() + { + var length = Length.From(314.158999, LengthTypeUnit.Foot); + + length.Value.Should().Be(314.158999); + length.Unit.Should().Be(LengthTypeUnit.Foot); + } + + [Fact] + public void Length_Constructor_WithUnitInFeet_ReturnsSameValueAndUnit() + { + var length = Length.FromFeet(314.158999); + + length.Value.Should().Be(314.158999); + length.Unit.Should().Be(LengthTypeUnit.Foot); + } + + [Fact] + public void Length_Constructor_WithUnitInMeters_ReturnsSameValueAndUnit() + { + var length = Length.FromMeters(95.755663); + + length.Value.Should().Be(95.755663); + length.Unit.Should().Be(LengthTypeUnit.Meter); + } + + [Fact] + public void Length_Constructor_WithNegativeValueInput_ThrowsException() + { + var action = () => Length.From(-100); + + action.Should().Throw() + .And.Errors.Should().BeEquivalentTo(new[] { new ValidationFailure("Value", "Could not create a Nox Length type as negative length value -100 is not allowed.") }); + } + + [Fact] + public void Length_Constructor_WithNaNValueInput_ThrowsException() + { + var action = () => Length.From(double.NaN); + + action.Should().Throw() + .And.Errors.Should().BeEquivalentTo(new[] { new ValidationFailure("Value", "Could not create a Nox type as value NaN is not allowed.") }); + } + + [Fact] + public void Length_Constructor_WithPositiveInfinityValueInput_ThrowsException() + { + var action = () => Length.From(double.PositiveInfinity); + + + action.Should().Throw() + .And.Errors.Should().BeEquivalentTo(new[] { new ValidationFailure("Value", "Could not create a Nox type as value Infinity is not allowed.") }); + } + + [Fact] + public void Length_Constructor_WithNegativeInfinityValueInput_ThrowsException() + { + var action = () => Length.From(double.NegativeInfinity); + + action.Should().Throw() + .And.Errors.Should().BeEquivalentTo(new[] { new ValidationFailure("Value", "Could not create a Nox type as value Infinity is not allowed.") }); + } + + [Fact] + public void Length_Constructor_WithWithUnsupportedUnitInput_ThrowsException() + { + var action = () => Length.From(95.755663, (LengthTypeUnit)1001); + + action.Should().Throw() + .And.Errors.Should().BeEquivalentTo(new[] { new ValidationFailure("Unit", "Could not create a Nox Length type as unit 1001 is not supported.") }); + } + + [Fact] + public void Length_ToMeters_ReturnsValueInMeters() + { + var length = Length.FromMeters(95.755663); + + length.ToMeters().Should().Be(95.755663); + } + + [Fact] + public void Length_ToFeet_ReturnsValueInFeet() + { + var length = Length.FromMeters(95.755663); + + length.ToFeet().Should().Be(314.158999); + } + + [Theory] + [InlineData("en-US")] + [InlineData("pt-PT")] + public void Length_ValueInMeters_ToString_IsCultureIndepdendent(string culture) + { + void Test() + { + var length = Length.FromMeters(95.755663); + length.ToString().Should().Be("95.755663 m"); + } + + TestUtility.RunInCulture(Test, culture); + } + + [Theory] + [InlineData("en-US", "95.755663 m")] + [InlineData("pt-PT", "95,755663 m")] + public void Length_ValueInMeters_ToString_IsCultureDependent(string culture, string expected) + { + void Test() + { + var length = Length.FromMeters(95.755663); + length.ToString(new CultureInfo(culture)).Should().Be(expected); + } + + TestUtility.RunInCulture(Test, culture); + } + + [Theory] + [InlineData("en-US")] + [InlineData("pt-PT")] + public void Length_ValueInFeet_ToString_IsCultureIndependent(string culture) + { + void Test() + { + var length = Length.FromFeet(314.158999); + length.ToString().Should().Be("314.158999 ft"); + } + + TestUtility.RunInCulture(Test, culture); + } + + [Theory] + [InlineData("en-US", "314.158999 ft")] + [InlineData("pt-PT", "314,158999 ft")] + public void Length_ValueInFeet_ToString_IsCultureDependent(string culture, string expected) + { + void Test() + { + var length = Length.FromFeet(314.158999); + length.ToString(new CultureInfo(culture)).Should().Be(expected); + } + + TestUtility.RunInCulture(Test, culture); + } + + [Fact] + public void Length_Equality_SpecifyingLengthUnit_WithSameUnit_Tests() { + var length1 = Length.FromMeters(95.755663); + + var length2 = Length.FromMeters(95.755663); + + AssertAreEquivalent(length1, length2); + } + + [Fact] + public void Length_Equality_SpecifyingLengthUnit_WithDifferentUnit_Tests() + { + var length1 = Length.FromMeters(95.755663); + + var length2 = Length.FromFeet(314.158999); + + AssertAreEquivalent(length1, length2); + } + + [Fact] + public void Length_NonEquality_SpecifyingLengthUnit_WithSameUnit_Tests() + { + var length1 = Length.FromMeters(95.755663); + + var length2 = Length.FromMeters(314.158999); + + AssertAreNotEquivalent(length1, length2); + } + + [Fact] + public void Length_NonEquality_SpecifyingLengthUnit_WithDifferentUnit_Tests() + { + var length1 = Length.FromMeters(95.755663); + + var length2 = Length.FromFeet(95.755663); + + AssertAreNotEquivalent(length1, length2); + } + + private static void AssertAreEquivalent(Length expected, Length actual) + { + actual.Should().Be(expected); + + expected.Equals(actual).Should().BeTrue(); + + actual.Equals(expected).Should().BeTrue(); + + (expected == actual).Should().BeTrue(); + + (expected != actual).Should().BeFalse(); + } + + private static void AssertAreNotEquivalent(Length expected, Length actual) + { + actual.Should().NotBe(expected); + + expected.Equals(actual).Should().BeFalse(); + + actual.Equals(expected).Should().BeFalse(); + + (expected == actual).Should().BeFalse(); + (expected != actual).Should().BeTrue(); } -} \ No newline at end of file +}