Skip to content
This repository has been archived by the owner on Jul 5, 2023. It is now read-only.

Commit

Permalink
Merge pull request #66 from NoxOrg/feature/Percentage
Browse files Browse the repository at this point in the history
Percentage implementation
  • Loading branch information
2 parents 24c34f2 + 3abd919 commit 3757bc4
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace Nox.Types.EntityFramework.Types;

public class PercentageConverter : ValueConverter<Percentage, float>
{
/// <summary>
/// Initializes a new instance of the <see cref="PercentageConverter" /> class.
/// </summary>
public PercentageConverter() : base(percentage => percentage.Value, percentageValue => Percentage.From(percentageValue))
{
}
}
67 changes: 64 additions & 3 deletions src/Nox.Types/Types/Percentage/Percentage.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,70 @@
using System;
using System.Globalization;

namespace Nox.Types;

/// <summary>
/// Represents a Nox <see cref="Percentage"/> type and value object.
/// </summary>
public sealed class Percentage : ValueObject<float, Percentage>
{
private PercentageTypeOptions _percentageTypeOptions = new();

/// <summary>
/// Creates a new instance of <see cref="Percentage"/> class with the specified values.
/// </summary>
/// <param name="value">The value to create the <see cref="Percentage"/> with</param>
/// <returns></returns>
/// <exception cref="TypeValidationException"></exception>
public static Percentage From(float value, PercentageTypeOptions options)
{
var newObject = new Percentage
{
Value = value,
_percentageTypeOptions = options
};

var validationResult = newObject.Validate();

if (!validationResult.IsValid)
{
throw new TypeValidationException(validationResult.Errors);
}

return newObject;
}

/// <summary>
/// Represents a Nox <see cref="Percentage"/> type and value object.
/// Validates a <see cref="Percentage"/> object.
/// </summary>
/// <remarks>Placeholder, needs to be implemented</remarks>
public sealed class Percentage : ValueObject<float, Percentage>
/// <returns>true if the <see cref="Percentage"/> value is valid.</returns>
internal override ValidationResult Validate()
{
var result = base.Validate();

if (Value < _percentageTypeOptions.MinValue && !float.IsNaN(Value) && !float.IsInfinity(Value))
{
result.Errors.Add(new ValidationFailure(nameof(Value), $"Could not create a Nox Percentage type as value {Value} is less than than the minimum specified value of {_percentageTypeOptions.MinValue}"));
}

if (Value > _percentageTypeOptions.MaxValue && !float.IsNaN(Value) && !float.IsInfinity(Value))
{
result.Errors.Add(new ValidationFailure(nameof(Value), $"Could not create a Nox Percentage type a value {Value} is greater than than the maximum specified value of {_percentageTypeOptions.MaxValue}"));
}

Value = (float)Math.Round(Value, _percentageTypeOptions.Digits);

return result;
}

public override string ToString()
=> $"{(Value * 100).ToString($"0.{new string('#', _percentageTypeOptions.Digits)}", CultureInfo.InvariantCulture)}%";

/// <summary>
/// Returns a string representation of the <see cref="Percentage"/> object using the specified <see cref="IFormatProvider"/>.
/// </summary>
/// <param name="formatProvider">The format provider for the length value.</param>
/// <returns>A string representation of the <see cref="Percentage"/> object with the value formatted using the specified <see cref="IFormatProvider"/>.</returns>
public string ToString(IFormatProvider formatProvider)
=> $"{Value.ToString(formatProvider)}%";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Nox.Types;
public class PercentageTypeOptions
{
public static readonly float DefaultMinValue = 0;
public static readonly float DefaultMaxValue = 1;
public float MinValue { get; set; } = DefaultMinValue;
public float MaxValue { get; set; } = DefaultMaxValue;

public int Digits { get; set; } = 2;
}
88 changes: 86 additions & 2 deletions tests/Nox.Types.Tests/Types/Percentage/PercentageTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,95 @@
// ReSharper disable once CheckNamespace
using FluentAssertions;
using System.Globalization;

namespace Nox.Types.Tests.Types;

public class PercentageTests
{
[Fact]
public void When_Create_Should()
public void Percentage_Constructor_ReturnsSameValue()
{
var testPercentage = 0.5f;

var number = Percentage.From(testPercentage);

number.Value.Should().Be(testPercentage);
}

[Fact]
public void Percentage_Constructor_ThrowsException_WhenValueExceedsMaxAllowed()
{
var testPercentage = 3.2f;

var action = () => Percentage.From(testPercentage);

action.Should().Throw<TypeValidationException>()
.And.Errors.Should().BeEquivalentTo(new[] { new ValidationFailure("Value", "Could not create a Nox Percentage type a value 3.2 is greater than than the maximum specified value of 1") });

}

[Fact]
public void Percentage_Constructor_ThrowsException_WhenValueIsLessThanMinAllowed()
{
var testPercentage = -0.3f;

var action = () => Percentage.From(testPercentage);

action.Should().Throw<TypeValidationException>()
.And.Errors.Should().BeEquivalentTo(new[] { new ValidationFailure("Value", "Could not create a Nox Percentage type as value -0.3 is less than than the minimum specified value of 0") });
}

[Fact]
public void Percentage_Constructor_RoundsFloatValues_WhenConstructedWithFloatInput()
{
var testPercentage = 0.4f;

var percentage = Percentage.From(testPercentage);

percentage.Value.Should().Be(0.4f);
}

[Fact]
public void Percentage_ToString_Returns_Value()
{
void Test()
{
var pecentageValue = 0.45f;

var percentage = Percentage.From(pecentageValue);

var percentageAsString = percentage.ToString();

Assert.Equal("45%", percentageAsString);
}

TestUtility.RunInInvariantCulture(Test);
}

[Theory]
[InlineData("en-US")]
[InlineData("pt-PT")]
public void Percentage_ValueInFloat_ToString_IsCultureIndepdendent(string culture)
{
void Test()
{
var percentage = Percentage.From(0.25f);
percentage.ToString().Should().Be("25%");
}

TestUtility.RunInCulture(Test, culture);
}

[Theory]
[InlineData("en-US", "0.43%")]
[InlineData("pt-PT", "0,43%")]
public void Percentage_ValueInFloat_ToString_IsCultureDependent(string culture, string expected)
{
void Test()
{
var percentage = Percentage.From(0.43f);
percentage.ToString(new CultureInfo(culture)).Should().Be(expected);
}

TestUtility.RunInCulture(Test, culture);
}
}

0 comments on commit 3757bc4

Please sign in to comment.