Skip to content

Commit 7fe8641

Browse files
Wilhelm Wiensangularsen
authored andcommitted
Add QuantityTypeConverter (#644)
Implements TypeConverter for IQuantity to convert from/to strings in a .NET application's presentation layer. https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.typeconverter?view=netframework-4.8
1 parent fa048aa commit 7fe8641

File tree

3 files changed

+658
-0
lines changed

3 files changed

+658
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Licensed under MIT No Attribution, see LICENSE file at the root.
2+
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.ComponentModel;
7+
using System.Text;
8+
9+
namespace UnitsNet.Tests.Helpers
10+
{
11+
/// <summary>
12+
/// Is used to imitate e property with attributes
13+
/// </summary>
14+
public class TypeDescriptorContext : ITypeDescriptorContext
15+
{
16+
public class PropertyDescriptor_ : PropertyDescriptor
17+
{
18+
public PropertyDescriptor_(string name, Attribute[] attributes) : base(name, attributes)
19+
{
20+
}
21+
22+
public override Type ComponentType => throw new NotImplementedException();
23+
24+
public override bool IsReadOnly => throw new NotImplementedException();
25+
26+
public override Type PropertyType => throw new NotImplementedException();
27+
28+
public override bool CanResetValue(object component)
29+
{
30+
throw new NotImplementedException();
31+
}
32+
33+
public override object GetValue(object component)
34+
{
35+
throw new NotImplementedException();
36+
}
37+
38+
public override void ResetValue(object component)
39+
{
40+
throw new NotImplementedException();
41+
}
42+
43+
public override void SetValue(object component, object value)
44+
{
45+
throw new NotImplementedException();
46+
}
47+
48+
public override bool ShouldSerializeValue(object component)
49+
{
50+
throw new NotImplementedException();
51+
}
52+
}
53+
54+
public TypeDescriptorContext(string name, Attribute[] attributes)
55+
{
56+
PropertyDescriptor = new PropertyDescriptor_(name, attributes);
57+
}
58+
59+
public IContainer Container => throw new NotImplementedException();
60+
61+
public object Instance => throw new NotImplementedException();
62+
63+
public PropertyDescriptor PropertyDescriptor { get; set; }
64+
65+
public object GetService(Type serviceType)
66+
{
67+
throw new NotImplementedException();
68+
}
69+
70+
public void OnComponentChanged()
71+
{
72+
throw new NotImplementedException();
73+
}
74+
75+
public bool OnComponentChanging()
76+
{
77+
throw new NotImplementedException();
78+
}
79+
}
80+
}
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
// Licensed under MIT No Attribution, see LICENSE file at the root.
2+
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.
3+
4+
using System;
5+
using System.ComponentModel;
6+
using System.Globalization;
7+
using System.Reflection;
8+
using UnitsNet.Tests.Helpers;
9+
using Xunit;
10+
11+
namespace UnitsNet.Tests
12+
{
13+
public class QuantityTypeConverterTest
14+
{
15+
// https://stackoverflow.com/questions/3612909/why-is-this-typeconverter-not-working
16+
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
17+
{
18+
AppDomain domain = (AppDomain)sender;
19+
foreach (Assembly asm in domain.GetAssemblies())
20+
{
21+
if (asm.FullName == args.Name)
22+
{
23+
return asm;
24+
}
25+
}
26+
return null;
27+
}
28+
29+
static QuantityTypeConverterTest()
30+
{
31+
// NOTE: After this, you can use your TypeConverter.
32+
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
33+
}
34+
35+
/// <summary>
36+
/// Is used for tests that are culture dependent
37+
/// </summary>
38+
private static CultureInfo culture = CultureInfo.GetCultureInfo("en-US");
39+
40+
[Theory]
41+
[InlineData(typeof(string), true)]
42+
[InlineData(typeof(double), false)]
43+
[InlineData(typeof(object), false)]
44+
[InlineData(typeof(float), false)]
45+
[InlineData(typeof(Length), false)]
46+
public void CanConvertFrom_GivenSomeTypes(Type value, bool expectedResult)
47+
{
48+
var converter = new QuantityTypeConverter<Length>();
49+
50+
bool canConvertFrom = converter.CanConvertFrom(value);
51+
52+
Assert.Equal(expectedResult, canConvertFrom);
53+
}
54+
55+
[Theory]
56+
[InlineData(typeof(string), true)]
57+
[InlineData(typeof(double), false)]
58+
[InlineData(typeof(object), false)]
59+
[InlineData(typeof(float), false)]
60+
[InlineData(typeof(Length), false)]
61+
public void CanConvertTo_GivenSomeTypes(Type value, bool expectedResult)
62+
{
63+
var converter = new QuantityTypeConverter<Length>();
64+
65+
bool canConvertTo = converter.CanConvertTo(value);
66+
67+
Assert.Equal(expectedResult, canConvertTo);
68+
}
69+
70+
[Theory]
71+
[InlineData("1mm", 1, Units.LengthUnit.Millimeter)]
72+
[InlineData("1m", 1, Units.LengthUnit.Meter)]
73+
[InlineData("1", 1, Units.LengthUnit.Meter)]
74+
[InlineData("1km", 1, Units.LengthUnit.Kilometer)]
75+
public void ConvertFrom_GivenQuantityStringAndContextWithNoAttributes_ReturnsQuantityWithBaseUnitIfNotSpecified(string str, double expectedValue, Enum expectedUnit)
76+
{
77+
var converter = new QuantityTypeConverter<Length>();
78+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
79+
80+
var convertedValue = (Length)converter.ConvertFrom(context, culture, str);
81+
82+
Assert.Equal(expectedValue, convertedValue.Value);
83+
Assert.Equal(expectedUnit, convertedValue.Unit);
84+
}
85+
86+
[Theory]
87+
[InlineData("1mm", 1, Units.LengthUnit.Millimeter)]
88+
[InlineData("1m", 1, Units.LengthUnit.Meter)]
89+
[InlineData("1", 1, Units.LengthUnit.Centimeter)]
90+
[InlineData("1km", 1, Units.LengthUnit.Kilometer)]
91+
public void ConvertFrom_GivenQuantityStringAndContextWithDefaultUnitAttribute_ReturnsQuantityWithGivenDefaultUnitIfNotSpecified(string str, double expectedValue, Enum expectedUnit)
92+
{
93+
var converter = new QuantityTypeConverter<Length>();
94+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[]
95+
{
96+
new DefaultUnitAttribute(Units.LengthUnit.Centimeter)
97+
});
98+
99+
var convertedValue = (Length)converter.ConvertFrom(context, culture, str);
100+
101+
Assert.Equal(expectedValue, convertedValue.Value);
102+
Assert.Equal(expectedUnit, convertedValue.Unit);
103+
}
104+
105+
[Theory]
106+
[InlineData("1mm", 0.001, Units.LengthUnit.Meter)]
107+
[InlineData("1m", 1, Units.LengthUnit.Meter)]
108+
[InlineData("1", 0.01, Units.LengthUnit.Meter)]
109+
[InlineData("1km", 1000, Units.LengthUnit.Meter)]
110+
public void ConvertFrom_GivenQuantityStringAndContextWithDefaultUnitAndConvertToUnitAttributes_ReturnsQuantityConvertedToUnit(string str, double expectedValue, Enum expectedUnit)
111+
{
112+
var converter = new QuantityTypeConverter<Length>();
113+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[]
114+
{
115+
new DefaultUnitAttribute(Units.LengthUnit.Centimeter),
116+
new ConvertToUnitAttribute(Units.LengthUnit.Meter)
117+
});
118+
119+
var convertedValue = (Length)converter.ConvertFrom(context, culture, str);
120+
121+
Assert.Equal(expectedValue, convertedValue.Value);
122+
Assert.Equal(expectedUnit, convertedValue.Unit);
123+
}
124+
125+
[Fact]
126+
public void ConvertFrom_GivenEmptyString_ThrowsNotSupportedException()
127+
{
128+
var converter = new QuantityTypeConverter<Length>();
129+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
130+
131+
Assert.Throws<NotSupportedException>(() => converter.ConvertFrom(context, culture, ""));
132+
}
133+
134+
[Fact]
135+
public void ConvertFrom_GivenWrongQuantity_ThrowsArgumentException()
136+
{
137+
var converter = new QuantityTypeConverter<Length>();
138+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
139+
140+
Assert.Throws<ArgumentException>(() => converter.ConvertFrom(context, culture, "1m^2"));
141+
}
142+
143+
[Theory]
144+
[InlineData(typeof(Length))]
145+
[InlineData(typeof(IQuantity))]
146+
[InlineData(typeof(object))]
147+
public void ConvertTo_GivenWrongType_ThrowsNotSupportedException(Type value)
148+
{
149+
var converter = new QuantityTypeConverter<Length>();
150+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
151+
Length length = Length.FromMeters(1);
152+
153+
Assert.Throws<NotSupportedException>(() => converter.ConvertTo(length, value));
154+
}
155+
156+
[Fact]
157+
public void ConvertTo_GivenStringType_ReturnsQuantityString()
158+
{
159+
var converter = new QuantityTypeConverter<Length>();
160+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
161+
Length length = Length.FromMeters(1);
162+
163+
string convertedQuantity = (string)converter.ConvertTo(length, typeof(string));
164+
165+
Assert.Equal("1 m", convertedQuantity);
166+
}
167+
168+
[Fact]
169+
public void ConvertTo_GivenSomeQuantitysAndContextWithNoAttributes_ReturnsQuantityStringInUnitOfQuantity()
170+
{
171+
var converter = new QuantityTypeConverter<Length>();
172+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
173+
Length length = Length.FromMeters(1);
174+
175+
string convertedQuantity = (string)converter.ConvertTo(context, culture, length, typeof(string));
176+
177+
Assert.Equal("1 m", convertedQuantity);
178+
}
179+
180+
[Fact]
181+
public void ConvertTo_TestDisplayAsFormatting_ReturnsQuantityStringWithDisplayUnitDefaultFormating()
182+
{
183+
var converter = new QuantityTypeConverter<Length>();
184+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[]
185+
{
186+
new DisplayAsUnitAttribute(Units.LengthUnit.Decimeter)
187+
});
188+
Length length = Length.FromMeters(1);
189+
190+
string convertedQuantity = (string)converter.ConvertTo(context, culture, length, typeof(string));
191+
192+
Assert.Equal("10 dm", convertedQuantity);
193+
}
194+
195+
[Fact]
196+
public void ConvertTo_TestDisplayAsFormatting_ReturnsQuantityStringWithDisplayUnitFormateAsValueOnly()
197+
{
198+
var converter = new QuantityTypeConverter<Length>();
199+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[]
200+
{
201+
new DisplayAsUnitAttribute(Units.LengthUnit.Decimeter, "v")
202+
});
203+
Length length = Length.FromMeters(1);
204+
205+
string convertedQuantity = (string)converter.ConvertTo(context, culture, length, typeof(string));
206+
207+
Assert.Equal("10", convertedQuantity);
208+
}
209+
210+
[Fact]
211+
public void ConvertTo_TestDisplayAsFormattingWithoutDefinedUnit_ReturnsQuantityStringWithQuantetiesUnitAndFormatedAsValueOnly()
212+
{
213+
var converter = new QuantityTypeConverter<Length>();
214+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[]
215+
{
216+
new DisplayAsUnitAttribute(null, "v")
217+
});
218+
Length length = Length.FromMeters(1);
219+
220+
string convertedQuantity = (string)converter.ConvertTo(context, culture, length, typeof(string));
221+
222+
Assert.Equal("1", convertedQuantity);
223+
}
224+
225+
[Fact]
226+
public void ConvertTo_GivenSomeQuantitysAndContextWithDisplayAsUnitAttributes_ReturnsQuantityStringInSpecifiedDisplayUnit()
227+
{
228+
var converter = new QuantityTypeConverter<Length>();
229+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[]
230+
{
231+
new DisplayAsUnitAttribute(Units.LengthUnit.Decimeter)
232+
});
233+
Length length = Length.FromMeters(1);
234+
235+
string convertedQuantityDefaultCulture = (string)converter.ConvertTo(length, typeof(string));
236+
string convertedQuantitySpecificCulture = (string)converter.ConvertTo(context, culture, length, typeof(string));
237+
238+
Assert.Equal("1 m", convertedQuantityDefaultCulture);
239+
Assert.Equal("10 dm", convertedQuantitySpecificCulture);
240+
}
241+
242+
[Fact]
243+
public void ConvertFrom_GivenDefaultUnitAttributeWithWrongUnitType_ThrowsArgumentException()
244+
{
245+
var converter = new QuantityTypeConverter<Length>();
246+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[]
247+
{
248+
new DefaultUnitAttribute(Units.VolumeUnit.CubicMeter)
249+
});
250+
251+
Assert.Throws<ArgumentException>(() => converter.ConvertFrom(context, culture, "1"));
252+
}
253+
254+
[Fact]
255+
public void ConvertFrom_GivenStringWithPower_1()
256+
{
257+
var converter = new QuantityTypeConverter<Length>();
258+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
259+
260+
Assert.Equal(Length.FromMeters(1), converter.ConvertFrom(context, culture, "1m"));
261+
Assert.Equal(Length.FromMeters(1), converter.ConvertFrom(context, culture, "1m^1"));
262+
}
263+
264+
[Fact]
265+
public void ConvertFrom_GivenStringWithPower_2()
266+
{
267+
var converter = new QuantityTypeConverter<Area>();
268+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
269+
270+
Assert.Equal(Area.FromSquareMeters(1), converter.ConvertFrom(context, culture, "1m²"));
271+
Assert.Equal(Area.FromSquareMeters(1), converter.ConvertFrom(context, culture, "1m^2"));
272+
}
273+
274+
[Fact]
275+
public void ConvertFrom_GivenStringWithPower_3()
276+
{
277+
var converter = new QuantityTypeConverter<Volume>();
278+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
279+
280+
Assert.Equal(Volume.FromCubicMeters(1), converter.ConvertFrom(context, culture, "1m³"));
281+
Assert.Equal(Volume.FromCubicMeters(1), converter.ConvertFrom(context, culture, "1m^3"));
282+
}
283+
284+
[Fact]
285+
public void ConvertFrom_GivenStringWithPower_4()
286+
{
287+
var converter = new QuantityTypeConverter<AreaMomentOfInertia>();
288+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
289+
290+
Assert.Equal(AreaMomentOfInertia.FromMetersToTheFourth(1), converter.ConvertFrom(context, culture, "1m⁴"));
291+
Assert.Equal(AreaMomentOfInertia.FromMetersToTheFourth(1), converter.ConvertFrom(context, culture, "1m^4"));
292+
}
293+
294+
[Fact]
295+
public void ConvertFrom_GivenStringWithPower_minus1()
296+
{
297+
var converter = new QuantityTypeConverter<CoefficientOfThermalExpansion>();
298+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
299+
300+
Assert.Equal(CoefficientOfThermalExpansion.FromInverseKelvin(1), converter.ConvertFrom(context, culture, "1K⁻¹"));
301+
Assert.Equal(CoefficientOfThermalExpansion.FromInverseKelvin(1), converter.ConvertFrom(context, culture, "1K^-1"));
302+
}
303+
304+
[Fact]
305+
public void ConvertFrom_GivenStringWithPower_minus2()
306+
{
307+
var converter = new QuantityTypeConverter<MassFlux>();
308+
ITypeDescriptorContext context = new TypeDescriptorContext("SomeMemberName", new Attribute[] { });
309+
310+
Assert.Equal(MassFlux.FromKilogramsPerSecondPerSquareMeter(1), converter.ConvertFrom(context, culture, "1kg·s⁻¹·m⁻²"));
311+
Assert.Equal(MassFlux.FromKilogramsPerSecondPerSquareMeter(1), converter.ConvertFrom(context, culture, "1kg·s^-1·m^-2"));
312+
Assert.Equal(MassFlux.FromKilogramsPerSecondPerSquareMeter(1), converter.ConvertFrom(context, culture, "1kg*s^-1*m^-2"));
313+
}
314+
}
315+
}

0 commit comments

Comments
 (0)