Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QuantityTypeConverter #644

Merged
merged 12 commits into from
Apr 29, 2019
Merged
348 changes: 348 additions & 0 deletions UnitsNet.Tests/QuantityTypeConverterTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
// Licensed under MIT No Attribution, see LICENSE file at the root.
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Text;
using UnitsNet;
using Xunit;

namespace UnitsNet.Tests
{
public class QuantityTypeConverterTest
{
// https://stackoverflow.com/questions/3612909/why-is-this-typeconverter-not-working
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
AppDomain domain = (AppDomain)sender;
foreach (Assembly asm in domain.GetAssemblies())
{
if (asm.FullName == args.Name)
{
return asm;
}
}
return null;
}

static QuantityTypeConverterTest()
{
// NOTE: After this, you can use your typeconverter.
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}

static CultureInfo culture = CultureInfo.GetCultureInfo("en-US");

public class TypeDescriptorContext : ITypeDescriptorContext
{
public class PropertyDescriptor_ : PropertyDescriptor
{
public PropertyDescriptor_(string name, Attribute[] attributes) : base(name, attributes)
{

}

public override Type ComponentType => throw new NotImplementedException();

public override bool IsReadOnly => throw new NotImplementedException();

public override Type PropertyType => throw new NotImplementedException();

public override bool CanResetValue(object component)
{
throw new NotImplementedException();
}

public override object GetValue(object component)
{
throw new NotImplementedException();
}

public override void ResetValue(object component)
{
throw new NotImplementedException();
}

public override void SetValue(object component, object value)
{
throw new NotImplementedException();
}

public override bool ShouldSerializeValue(object component)
{
throw new NotImplementedException();
}
}

public TypeDescriptorContext(string name, Attribute[] attributes)
{
PropertyDescriptor = new PropertyDescriptor_(name, attributes);
}

public IContainer Container => throw new NotImplementedException();

public object Instance => throw new NotImplementedException();

public PropertyDescriptor PropertyDescriptor { get; set; }

public object GetService(Type serviceType)
{
throw new NotImplementedException();
}

public void OnComponentChanged()
{
throw new NotImplementedException();
}

public bool OnComponentChanging()
{
throw new NotImplementedException();
}
}

[Theory]
[InlineData(typeof(string))]
public void CanConvertFrom_GiveSomeTypes_ReturnsTrue(Type value)
{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();

Assert.True(unitsNETTypeConverter.CanConvertFrom(value));
}

[Theory]
[InlineData(typeof(double))]
[InlineData(typeof(object))]
[InlineData(typeof(float))]
[InlineData(typeof(Length))]
public void CanConvertFrom_GiveSomeTypes_ReturnsFalse(Type value)
{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();

Assert.False(unitsNETTypeConverter.CanConvertFrom(value));
}

[Theory]
[InlineData(typeof(string))]
public void CanConvertTo_GiveSomeTypes_ReturnsTrue(Type value)
{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();

Assert.True(unitsNETTypeConverter.CanConvertTo(value));
}

[Theory]
[InlineData(typeof(double))]
[InlineData(typeof(object))]
[InlineData(typeof(float))]
[InlineData(typeof(Length))]
public void CanConvertTo_GiveSomeTypes_ReturnsFalse(Type value)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as CanConvertFrom above

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not important, but you could add a second parameter bool expectedResult to these test methods and add the input like this [InlineData(typeof(float), false)].

{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();

Assert.False(unitsNETTypeConverter.CanConvertTo(value));
}

[Fact]
public void ConvertFrom_GiveSomeStrings()
{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });

Assert.Equal(Length.FromMillimeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1mm"));
Assert.Equal(Length.FromMeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1m"));
Assert.Equal(Length.FromMeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1"));
Assert.Equal(Length.FromKilometers(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1km"));

context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { new DefaultUnitAttribute(Units.LengthUnit.Centimeter) });
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make different context values separate test methods.
Something like

  • ConvertFrom_GivenQuantityStringAndContextWithNoAttributes_ReturnsQuantityWithBaseUnitIfNotSpecified
  • ConvertFrom_GivenQuantityStringAndContextWithDefaultUnitAttribute_ReturnsQuantityWithGivenDefaultUnitIfNotSpecified
  • ConvertFrom_GivenQuantityStringAndContextWithDefaultUnitAndConvertToUnitAttributes_ReturnsQuantityConvertedToUnit

Long names, maybe they can be shorter, but I'd rather have long descriptive names than short names that don't explain what they test.


Assert.Equal(Length.FromMillimeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1mm"));
Assert.Equal(Length.FromMeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1m"));
Assert.Equal(Length.FromCentimeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1"));
Assert.Equal(Length.FromKilometers(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1km"));

context = new TypeDescriptorContext("SomeMemberName", new Attribute[]
{
new DefaultUnitAttribute(Units.LengthUnit.Centimeter),
new ConvertToUnitAttribute(Units.LengthUnit.Meter)
});

Assert.Equal(Length.FromMeters(0.001), unitsNETTypeConverter.ConvertFrom(context, culture, "1mm"));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you meant to test that these values have the unit meters, then you need to explicitly test .Value and .Unit - since Length.FromCentimeters(100) == Length.FromMeters(1).

Assert.Equal(Length.FromMeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1m"));
Assert.Equal(Length.FromMeters(0.01), unitsNETTypeConverter.ConvertFrom(context, culture, "1"));
Assert.Equal(Length.FromMeters(1000), unitsNETTypeConverter.ConvertFrom(context, culture, "1km"));
}

[Fact]
public void ConvertFrom_GiveEmpyString()
{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });

Assert.Throws<NotSupportedException>(() => unitsNETTypeConverter.ConvertFrom(context, culture, ""));
}

[Fact]
public void ConvertFrom_GiveWrongQuantity()
{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });

Assert.Throws<ArgumentException>(() => unitsNETTypeConverter.ConvertFrom(context, culture, "1m^2"));
}

[Theory]
[InlineData(typeof(Length))]
[InlineData(typeof(IQuantity))]
[InlineData(typeof(object))]
public void ConvertTo_GiveWrongType_ThrowException(Type value)
{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
Length length = Length.FromMeters(1);

Assert.Throws<NotSupportedException>(() => unitsNETTypeConverter.ConvertTo(length, value));
}

[Theory]
[InlineData(typeof(string))]
public void ConvertTo_GiveRigthType_DoConvertion(Type value)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_ReturnsQuantityString

{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
Length length = Length.FromMeters(1);

Assert.Equal("1 m", unitsNETTypeConverter.ConvertTo(length, value));
}

[Fact]
public void ConvertTo_GiveSomeValues_ConvertToString()
{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
Length length = Length.FromMeters(1);

Assert.Equal("1 m", unitsNETTypeConverter.ConvertTo(length, typeof(string)));
Assert.Equal("1 m", unitsNETTypeConverter.ConvertTo(context, culture, length, typeof(string)));

context = new TypeDescriptorContext("SomeMemberName", new Attribute[]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, split into separate test cases for each context value.

{
new DefaultUnitAttribute(Units.LengthUnit.Centimeter),
new ConvertToUnitAttribute(Units.LengthUnit.Millimeter)
});

Assert.Equal("1 m", unitsNETTypeConverter.ConvertTo(length, typeof(string)));
Assert.Equal("1 m", unitsNETTypeConverter.ConvertTo(context, culture, length, typeof(string)));

context = new TypeDescriptorContext("SomeMemberName", new Attribute[]
{
new DefaultUnitAttribute(Units.LengthUnit.Centimeter),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is DefaultUnitAttribute used in these ConvertTo tests? I thought it controlled the default unit in ConvertFrom cases, for strings with only numbers?
Same with ConvertToUnitAttribute, is that used?

Copy link
Contributor Author

@chromhelm chromhelm Apr 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No they are not used.
This test is only makes sure that they don't influence the displayed unit.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's best to test only one thing, or at least minimize the variables of the test case. If you want to test that the other attributes don't have an effect in a particular setup then that can be a separate test method I think.

new ConvertToUnitAttribute(Units.LengthUnit.Millimeter),
new DisplayAsUnitAttribute(Units.LengthUnit.Decimeter)
});

Assert.Equal("1 m", unitsNETTypeConverter.ConvertTo(length, typeof(string)));
Assert.Equal("10 dm", unitsNETTypeConverter.ConvertTo(context, culture, length, typeof(string)));
}

[Fact]
public void ConvertTo_TestDisplayAsFormating_ConvertToFormatedString()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo Formatting and Formatted
Maybe rename to suffix _ReturnsQuantityStringWithDisplayUnit.

{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
Length length = Length.FromMeters(1);

context = new TypeDescriptorContext("SomeMemberName", new Attribute[]
{
new DisplayAsUnitAttribute(Units.LengthUnit.Decimeter)
});

Assert.Equal("10 dm", unitsNETTypeConverter.ConvertTo(context, culture, length, typeof(string)));

context = new TypeDescriptorContext("SomeMemberName", new Attribute[]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate test cases for context values.

{
new DisplayAsUnitAttribute(Units.LengthUnit.Decimeter, "v")
});

Assert.Equal("10", unitsNETTypeConverter.ConvertTo(context, culture, length, typeof(string)));
}


[Fact]
public void WrongUnitTypeInAttribut_DefaultUnit()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConvertFrom_GivenWrongDefaultUnit_ThrowsInvalidOperationException.
Here too, I think we should deciced on what exception types to use for invalid inputs. I think ArgumentException is good here too, since the arguments have value combinations we don't like.

{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { new DefaultUnitAttribute(Units.VolumeUnit.CubicMeter) });

Assert.Throws<InvalidOperationException>(() => unitsNETTypeConverter.ConvertFrom(context, culture, "1"));
}





[Fact]
public void ConvertFrom_GiveStringWithPower_1()
{
UnitsNetTypeConverter<Length> unitsNETTypeConverter = new UnitsNetTypeConverter<Length>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });

Assert.Equal(Length.FromMeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1m"));
Assert.Equal(Length.FromMeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1m^1"));
}

[Fact]
public void ConvertFrom_GiveStringWithPower_2()
{
UnitsNetTypeConverter<Area> unitsNETTypeConverter = new UnitsNetTypeConverter<Area>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });

Assert.Equal(Area.FromSquareMeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1m²"));
Assert.Equal(Area.FromSquareMeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1m^2"));
}

[Fact]
public void ConvertFrom_GiveStringWithPower_3()
{
UnitsNetTypeConverter<Volume> unitsNETTypeConverter = new UnitsNetTypeConverter<Volume>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });

Assert.Equal(Volume.FromCubicMeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1m³"));
Assert.Equal(Volume.FromCubicMeters(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1m^3"));
}

[Fact]
public void ConvertFrom_GiveStringWithPower_4()
{
UnitsNetTypeConverter<AreaMomentOfInertia> unitsNETTypeConverter = new UnitsNetTypeConverter<AreaMomentOfInertia>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });

Assert.Equal(AreaMomentOfInertia.FromMetersToTheFourth(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1m⁴"));
Assert.Equal(AreaMomentOfInertia.FromMetersToTheFourth(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1m^4"));
}

[Fact]
public void ConvertFrom_GiveStringWithPower_minus1()
{
UnitsNetTypeConverter<CoefficientOfThermalExpansion> unitsNETTypeConverter = new UnitsNetTypeConverter<CoefficientOfThermalExpansion>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });

Assert.Equal(CoefficientOfThermalExpansion.FromInverseKelvin(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1K⁻¹"));
Assert.Equal(CoefficientOfThermalExpansion.FromInverseKelvin(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1K^-1"));
}

[Fact]
public void ConvertFrom_GiveStringWithPower_minus2()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These power tests are nice 👍

{
UnitsNetTypeConverter<MassFlux> unitsNETTypeConverter = new UnitsNetTypeConverter<MassFlux>();
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });

Assert.Equal(MassFlux.FromKilogramsPerSecondPerSquareMeter(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1kg·s⁻¹·m⁻²"));
Assert.Equal(MassFlux.FromKilogramsPerSecondPerSquareMeter(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1kg·s^-1·m^-2"));
Assert.Equal(MassFlux.FromKilogramsPerSecondPerSquareMeter(1), unitsNETTypeConverter.ConvertFrom(context, culture, "1kg*s^-1*m^-2"));
}
}
}
Loading