diff --git a/ChartJs.Blazor.Tests/ChartJs.Blazor.Tests.csproj b/ChartJs.Blazor.Tests/ChartJs.Blazor.Tests.csproj
new file mode 100644
index 00000000..3f84eadc
--- /dev/null
+++ b/ChartJs.Blazor.Tests/ChartJs.Blazor.Tests.csproj
@@ -0,0 +1,24 @@
+
+
+
+ netcoreapp3.1
+
+ false
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
diff --git a/ChartJs.Blazor.Tests/ObjectEnumTests.Deserialization.cs b/ChartJs.Blazor.Tests/ObjectEnumTests.Deserialization.cs
new file mode 100644
index 00000000..9a54ecf4
--- /dev/null
+++ b/ChartJs.Blazor.Tests/ObjectEnumTests.Deserialization.cs
@@ -0,0 +1,122 @@
+using ChartJs.Blazor.ChartJS.Common.Enums;
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+using Xunit;
+
+namespace ChartJs.Blazor.Tests
+{
+ public partial class ObjectEnumTests
+ {
+ [Theory]
+ [InlineData("0", 0)]
+ [InlineData("10", 10)]
+ [InlineData("-1234567489", -1234567489)]
+ public void Deserialize_IntEnum_FromRoot(string json, int expectedValue)
+ {
+ // Act
+ TestObjectEnum objEnum = JsonConvert.DeserializeObject(json);
+
+ // Assert
+ Assert.True(objEnum.Equals(expectedValue)); // expects all the equality stuff to be correct
+ }
+
+ [Theory]
+ // [InlineData("0", 0.0)] this would fail because it gets serialized as int, not double
+ [InlineData("0.0", 0.0)]
+ [InlineData("123.456", 123.456)]
+ [InlineData("-654.321", -654.321)]
+ public void Deserialize_DoubleEnum_FromRoot(string json, double expectedValue)
+ {
+ // Act
+ TestObjectEnum objEnum = JsonConvert.DeserializeObject(json);
+
+ // Assert
+ Assert.True(objEnum.Equals(expectedValue)); // expects all the equality stuff to be correct
+ }
+
+ [Theory]
+ [InlineData("0", 0)]
+ [InlineData("10", 10)]
+ [InlineData("-1234567489", -1234567489)]
+ public void Deserialize_DoubleEnumThroughInt_FromRoot(string json, double expectedValue)
+ {
+ // Act
+ // the json would result in an int being deserialized but since there is no int
+ // that would fail. So instead it uses the double constructor.
+ DoubleStringEnum objEnum = JsonConvert.DeserializeObject(json);
+
+ // Assert
+ Assert.True(objEnum.Equals(expectedValue)); // expects all the equality stuff to be correct
+ }
+
+ [Theory]
+ [InlineData("\"Hello World!\"", "Hello World!")]
+ [InlineData("\"\\\"That's what!\\\", she said.\"", "\"That's what!\", she said.")]
+ [InlineData("\"¨öä$ü¨^'{}][\\\\|/-.,+-/*\"", "¨öä$ü¨^'{}][\\|/-.,+-/*")]
+ public void Deserialize_StringEnum_FromRoot(string json, string expectedValue)
+ {
+ // Act
+ TestObjectEnum objEnum = JsonConvert.DeserializeObject(json);
+
+ // Assert
+ Assert.True(objEnum.Equals(expectedValue)); // expects all the equality stuff to be correct
+ }
+
+ [Fact]
+ public void Deserialize_BigIntegerEnum_ThrowsNotSupported()
+ {
+ // Arrange
+ string json = $"{ulong.MaxValue}"; // bigger than long.MaxValue
+
+ // Act & Assert
+ Assert.Throws(() => JsonConvert.DeserializeObject(json));
+ }
+
+ [Fact]
+ public void Deserialize_JsonArray_ThrowsNotSupported()
+ {
+ // Arrange
+ const string json = "[1,2,3]";
+
+ // Act & Assert
+ Assert.Throws(() => JsonConvert.DeserializeObject(json));
+ }
+
+ [Fact]
+ public void Deserialize_JsonObject_ThrowsNotSupported()
+ {
+ // Arrange
+ string json = "{}";
+
+ // Act & Assert
+ Assert.Throws(() => JsonConvert.DeserializeObject(json));
+ }
+
+ [Fact]
+ public void Deserialize_Null_ReturnsNull()
+ {
+ // Arrange
+ string json = "null";
+
+ // Act
+ TestObjectEnum objEnum = JsonConvert.DeserializeObject(json);
+
+ // Assert
+ Assert.Null(objEnum);
+ }
+
+ [Fact]
+ public void Deserialize_Undefined_ReturnsNull()
+ {
+ // Arrange
+ string json = "undefined";
+
+ // Act
+ TestObjectEnum objEnum = JsonConvert.DeserializeObject(json);
+
+ // Assert
+ Assert.Null(objEnum);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ChartJs.Blazor.Tests/ObjectEnumTests.Equality.cs b/ChartJs.Blazor.Tests/ObjectEnumTests.Equality.cs
new file mode 100644
index 00000000..a495d1a0
--- /dev/null
+++ b/ChartJs.Blazor.Tests/ObjectEnumTests.Equality.cs
@@ -0,0 +1,245 @@
+using ChartJs.Blazor.ChartJS.Common.Enums;
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+using Xunit;
+
+namespace ChartJs.Blazor.Tests
+{
+ public partial class ObjectEnumTests
+ {
+ [Fact]
+ public void Equals_IntEnumAndIntEnum_ReturnsTrue()
+ {
+ // Arrange
+ const int ExampleIntValue = 10;
+ var a = TestObjectEnum.Int(ExampleIntValue);
+ var b = TestObjectEnum.Int(ExampleIntValue);
+
+ // Act
+ bool equal = a.Equals(b);
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void Equals_IntEnumAndInt_ReturnsTrue()
+ {
+ // Arrange
+ const int ExampleIntValue = 10;
+ var a = TestObjectEnum.Int(ExampleIntValue);
+
+ // Act
+ bool equal = a.Equals(ExampleIntValue);
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void Equals_StringEnumAndStringEnum_ReturnsTrue()
+ {
+ // Arrange
+ var a = TestObjectEnum.Auto;
+ var b = TestObjectEnum.Auto; // different instance, same inner value
+
+ // Act
+ bool equal = a.Equals(b);
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void Equals_StringEnumAndString_ReturnsTrue()
+ {
+ // Arrange
+ const string ExampleStringValue = "abcdefg";
+ var a = TestObjectEnum.CustomString(ExampleStringValue);
+
+ // Act
+ bool equal = a.Equals(ExampleStringValue);
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void Equals_DoubleEnumAndDoubleEnum_ReturnsTrue()
+ {
+ // Arrange
+ const double ExampleDoubleValue = 123.456;
+ var a = TestObjectEnum.Double(ExampleDoubleValue);
+ var b = TestObjectEnum.Double(ExampleDoubleValue);
+
+ // Act
+ bool equal = a.Equals(b);
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void Equals_DoubleEnumAndDouble_ReturnsTrue()
+ {
+ // Arrange
+ const double ExampleDoubleValue = 123.456;
+ var a = TestObjectEnum.Double(ExampleDoubleValue);
+
+ // Act
+ bool equal = a.Equals(ExampleDoubleValue);
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void Equals_EnumAndNull_ReturnsFalse()
+ {
+ // Arrange
+ var a = TestObjectEnum.CustomObject(new object());
+
+ // Act
+ bool equal = a.Equals(null);
+
+ // Assert
+ Assert.False(equal);
+ }
+
+ [Fact]
+ public void Equals_SameReference_ReturnsTrue()
+ {
+ // Arrange
+ var a = TestObjectEnum.CustomObject(new object());
+ var b = a;
+
+ // Act
+ bool equal = a.Equals(b);
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void EqualityOperator_IntEnumAndIntEnum_ReturnsTrue()
+ {
+ // Arrange
+ const int ExampleIntValue = 10;
+ var a = TestObjectEnum.Int(ExampleIntValue);
+ var b = TestObjectEnum.Int(ExampleIntValue);
+
+ // Act
+ bool equal = a == b;
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void EqualityOperator_StringEnumAndStringEnum_ReturnsTrue()
+ {
+ // Arrange
+ var a = TestObjectEnum.Auto;
+ var b = TestObjectEnum.Auto; // different instance, same inner value
+
+ // Act
+ bool equal = a == b;
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void EqualityOperator_DoubleEnumAndDoubleEnum_ReturnsTrue()
+ {
+ // Arrange
+ const double ExampleDoubleValue = 123.456;
+ var a = TestObjectEnum.Double(ExampleDoubleValue);
+ var b = TestObjectEnum.Double(ExampleDoubleValue);
+
+ // Act
+ bool equal = a == b;
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void EqualityOperator_EnumAndNull_ReturnsFalse()
+ {
+ // Arrange
+ var a = TestObjectEnum.CustomObject(new object());
+
+ // Act
+ bool equal = a == null;
+
+ // Assert
+ Assert.False(equal);
+ }
+
+ [Fact]
+ public void EqualityOperator_NullAndNull_ReturnsTrue()
+ {
+ // Arrange
+ TestObjectEnum a = null;
+
+ // Act
+ bool equal = a == null;
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void EqualityOperator_SameReference_ReturnsTrue()
+ {
+ // Arrange
+ var a = TestObjectEnum.CustomObject(new object());
+ var b = a;
+
+ // Act
+ bool equal = a == b;
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Theory]
+ [InlineData(10)]
+ [InlineData(123.456)]
+ [InlineData("asdf")]
+ [InlineData(false)]
+ public void GetHashCode_InnerValueAndEnum_Equals(object value)
+ {
+ // Arrange
+ TestObjectEnum objEnum = TestObjectEnum.CustomObject(value);
+
+ // Act
+ int hashCodeValue = value.GetHashCode();
+ int hashCodeEnum = objEnum.GetHashCode();
+
+ // Assert
+ Assert.Equal(hashCodeValue, hashCodeEnum);
+ }
+
+ [Theory]
+ [InlineData(-10)]
+ [InlineData(-654.321)]
+ [InlineData("fdsa")]
+ [InlineData(true)]
+ public void GetHashCode_EnumAndEnum_Equals(object value)
+ {
+ // Arrange
+ var a = TestObjectEnum.CustomObject(value);
+ var b = TestObjectEnum.CustomObject(value);
+
+ // Act
+ int hashA = a.GetHashCode();
+ int hashB = b.GetHashCode();
+
+ // Assert
+ Assert.Equal(hashA, hashB);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ChartJs.Blazor.Tests/ObjectEnumTests.General.cs b/ChartJs.Blazor.Tests/ObjectEnumTests.General.cs
new file mode 100644
index 00000000..5d5f498b
--- /dev/null
+++ b/ChartJs.Blazor.Tests/ObjectEnumTests.General.cs
@@ -0,0 +1,28 @@
+using ChartJs.Blazor.ChartJS.Common.Enums;
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+using Xunit;
+
+namespace ChartJs.Blazor.Tests
+{
+ public partial class ObjectEnumTests
+ {
+ [Fact]
+ public void Construct_NullValue_ThrowsArgumentNullException()
+ {
+ Assert.Throws(() => TestObjectEnum.Null);
+ Assert.Throws(() => TestObjectEnum.CustomString(null));
+ }
+
+ [Fact]
+ public void Deserialize_EnumWithoutConstructor_ThrowsNotSupportedException()
+ {
+ // Arrange
+ const string json = "\"asdf\"";
+
+ // Act & Assert
+ Assert.Throws(() => JsonConvert.DeserializeObject(json));
+ }
+ }
+}
diff --git a/ChartJs.Blazor.Tests/ObjectEnumTests.Serialization.cs b/ChartJs.Blazor.Tests/ObjectEnumTests.Serialization.cs
new file mode 100644
index 00000000..53ba9234
--- /dev/null
+++ b/ChartJs.Blazor.Tests/ObjectEnumTests.Serialization.cs
@@ -0,0 +1,105 @@
+using ChartJs.Blazor.ChartJS.Common.Enums;
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+using Xunit;
+
+namespace ChartJs.Blazor.Tests
+{
+ public partial class ObjectEnumTests
+ {
+ [Theory]
+ [InlineData(0)]
+ [InlineData(10)]
+ [InlineData(-10)]
+ [InlineData(int.MinValue)]
+ [InlineData(int.MaxValue)]
+ public void Serialize_IntEnum_AsRoot(int value)
+ {
+ // Arrange
+ TestObjectEnum objEnum = TestObjectEnum.Int(value);
+
+ // Act
+ string serialized = JsonConvert.SerializeObject(objEnum);
+
+ // Assert
+ Assert.Equal(value.ToString(CultureInfo.InvariantCulture), serialized);
+ }
+
+ [Theory]
+ [InlineData(123.456)]
+ [InlineData(-654.321)]
+ [InlineData(double.MinValue)]
+ [InlineData(double.MaxValue)]
+ public void Serialize_DoubleEnum_AsRoot(double value)
+ {
+ // Arrange
+ TestObjectEnum objEnum = TestObjectEnum.Double(value);
+
+ // Act
+ string serialized = JsonConvert.SerializeObject(objEnum);
+
+ // Assert
+ Assert.Equal(value.ToString(CultureInfo.InvariantCulture), serialized);
+ }
+
+ [Fact]
+ public void Serialize_DoubleEnumZero_AsRoot()
+ {
+ // Arrange
+ TestObjectEnum objEnum = TestObjectEnum.Double(0);
+
+ // Act
+ string serialized = JsonConvert.SerializeObject(objEnum);
+
+ // Assert
+ Assert.Equal("0.0", serialized);
+ }
+
+ [Fact]
+ public void Serialize_FloatEnum_ThrowsNotSupported()
+ {
+ // Arrange
+ TestObjectEnum objEnum = TestObjectEnum.Float(15.2f);
+
+ // Act & Assert
+ Assert.Throws(() => JsonConvert.SerializeObject(objEnum));
+ }
+
+ [Theory]
+ [InlineData("foo")]
+ [InlineData("bar")]
+ [InlineData("whomst'd've'ly'yaint'nt'ed'ies's'y'es")]
+ [InlineData("\"That's what!\", she said.")]
+ [InlineData("\uD83D\uDE42 emoji shenanigans")]
+ [InlineData("¨öä$ü¨^'{}][\\|/-.,+-/*")]
+ public void Serialize_StringEnum_AsRoot(string value)
+ {
+ // Arrange
+ TestObjectEnum objEnum = TestObjectEnum.CustomString(value);
+ string escapedValue = JsonConvert.ToString(value);
+
+ // Act
+ string serialized = JsonConvert.SerializeObject(objEnum);
+
+ // Assert
+ Assert.Equal(escapedValue, serialized);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void Serialize_BoolEnum_AsRoot(bool value)
+ {
+ // Arrange
+ TestObjectEnum objEnum = TestObjectEnum.CustomBool(value);
+ string escapedValue = JsonConvert.ToString(value);
+
+ // Act
+ string serialized = JsonConvert.SerializeObject(objEnum);
+
+ // Assert
+ Assert.Equal(escapedValue, serialized);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ChartJs.Blazor.Tests/ObjectEnumTests.TestClasses.cs b/ChartJs.Blazor.Tests/ObjectEnumTests.TestClasses.cs
new file mode 100644
index 00000000..a7628fe7
--- /dev/null
+++ b/ChartJs.Blazor.Tests/ObjectEnumTests.TestClasses.cs
@@ -0,0 +1,48 @@
+using ChartJs.Blazor.ChartJS.Common.Enums;
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+using Xunit;
+
+namespace ChartJs.Blazor.Tests
+{
+ public partial class ObjectEnumTests
+ {
+ private class TestObjectEnum : ObjectEnum
+ {
+ public static TestObjectEnum Null => new TestObjectEnum(null);
+ public static TestObjectEnum True => new TestObjectEnum(true);
+ public static TestObjectEnum Auto => new TestObjectEnum("auto");
+ public static TestObjectEnum CustomBool(bool value) => new TestObjectEnum(value);
+ public static TestObjectEnum Int(int value) => new TestObjectEnum(value);
+ public static TestObjectEnum Double(double value) => new TestObjectEnum(value);
+ public static TestObjectEnum Float(float value) => new TestObjectEnum(value);
+ public static TestObjectEnum CustomString(string value) => new TestObjectEnum(value);
+ // Only for testing!
+ public static TestObjectEnum CustomObject(object value) => new TestObjectEnum(value);
+
+ private TestObjectEnum(string value) : base(value) { }
+ private TestObjectEnum(int value) : base(value) { }
+ private TestObjectEnum(float value) : base(value) { }
+ private TestObjectEnum(double value) : base(value) { }
+ private TestObjectEnum(bool value) : base(value) { }
+ // Only for testing!
+ private TestObjectEnum(object value) : base(value) { }
+ }
+
+ private class DoubleStringEnum : ObjectEnum
+ {
+ public static DoubleStringEnum CustomString(string value) => new DoubleStringEnum(value);
+ public static DoubleStringEnum CustomDouble(double value) => new DoubleStringEnum(value);
+
+ private DoubleStringEnum(string value) : base(value) { }
+ private DoubleStringEnum(double value) : base(value) { }
+ }
+
+ private class EnumWithoutConstructor : ObjectEnum
+ {
+ // no suitable (supported) constructor
+ private EnumWithoutConstructor(object value) : base(value) { }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ChartJs.Blazor.Tests/StringEnumTests.Deserialization.cs b/ChartJs.Blazor.Tests/StringEnumTests.Deserialization.cs
new file mode 100644
index 00000000..df1e6f2f
--- /dev/null
+++ b/ChartJs.Blazor.Tests/StringEnumTests.Deserialization.cs
@@ -0,0 +1,91 @@
+using ChartJs.Blazor.ChartJS.Common.Enums;
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+using Xunit;
+
+namespace ChartJs.Blazor.Tests
+{
+ public partial class StringEnumTests
+ {
+ [Theory]
+ [InlineData("\"foo\"", "foo")]
+ [InlineData("\"Hello World!\"", "Hello World!")]
+ [InlineData("\"\\\"That's what!\\\", she said.\"", "\"That's what!\", she said.")]
+ [InlineData("\"¨öä$ü¨^'{}][\\\\|/-.,+-/*\"", "¨öä$ü¨^'{}][\\|/-.,+-/*")]
+ public void Deserialize_StringEnum_FromRoot(string json, string expectedValue)
+ {
+ // Act
+ TestStringEnum stringEnum = JsonConvert.DeserializeObject(json);
+
+ // Assert
+ Assert.True(stringEnum.Equals(expectedValue)); // expects all the equality stuff to be correct
+ }
+
+ [Fact]
+ public void Deserialize_Int_ThrowsNotSupported()
+ {
+ // Arrange
+ const string json = "0";
+
+ // Act & Assert
+ Assert.Throws(() => JsonConvert.DeserializeObject(json));
+ }
+
+ [Fact]
+ public void Deserialize_Double_ThrowsNotSupported()
+ {
+ // Arrange
+ const string json = "0.0";
+
+ // Act & Assert
+ Assert.Throws(() => JsonConvert.DeserializeObject(json));
+ }
+
+ [Fact]
+ public void Deserialize_JsonArray_ThrowsNotSupported()
+ {
+ // Arrange
+ const string json = "[1,2,3]";
+
+ // Act & Assert
+ Assert.Throws(() => JsonConvert.DeserializeObject(json));
+ }
+
+ [Fact]
+ public void Deserialize_JsonObject_ThrowsNotSupported()
+ {
+ // Arrange
+ string json = "{}";
+
+ // Act & Assert
+ Assert.Throws(() => JsonConvert.DeserializeObject(json));
+ }
+
+ [Fact]
+ public void Deserialize_Null_ReturnsNull()
+ {
+ // Arrange
+ string json = "null";
+
+ // Act
+ TestStringEnum stringEnum = JsonConvert.DeserializeObject(json);
+
+ // Assert
+ Assert.Null(stringEnum);
+ }
+
+ [Fact]
+ public void Deserialize_Undefined_ReturnsNull()
+ {
+ // Arrange
+ string json = "undefined";
+
+ // Act
+ TestStringEnum stringEnum = JsonConvert.DeserializeObject(json);
+
+ // Assert
+ Assert.Null(stringEnum);
+ }
+ }
+}
diff --git a/ChartJs.Blazor.Tests/StringEnumTests.Equality.cs b/ChartJs.Blazor.Tests/StringEnumTests.Equality.cs
new file mode 100644
index 00000000..1f0875f5
--- /dev/null
+++ b/ChartJs.Blazor.Tests/StringEnumTests.Equality.cs
@@ -0,0 +1,150 @@
+using ChartJs.Blazor.ChartJS.Common.Enums;
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+using Xunit;
+
+namespace ChartJs.Blazor.Tests
+{
+ public partial class StringEnumTests
+ {
+ [Fact]
+ public void Equals_StringEnumAndStringEnum_ReturnsTrue()
+ {
+ // Arrange
+ var a = TestStringEnum.Auto;
+ var b = TestStringEnum.Auto; // different instance, same inner value
+
+ // Act
+ bool equal = a.Equals(b);
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void Equals_StringEnumAndString_ReturnsTrue()
+ {
+ // Arrange
+ const string ExampleStringValue = "abcdefg";
+ var a = TestStringEnum.Custom(ExampleStringValue);
+
+ // Act
+ bool equal = a.Equals(ExampleStringValue);
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void Equals_EnumAndNull_ReturnsFalse()
+ {
+ // Arrange
+ var a = TestStringEnum.Custom(string.Empty);
+
+ // Act
+ bool equal = a.Equals(null);
+
+ // Assert
+ Assert.False(equal);
+ }
+
+ [Fact]
+ public void Equals_SameReference_ReturnsTrue()
+ {
+ // Arrange
+ var a = TestStringEnum.Custom(string.Empty);
+ var b = a;
+
+ // Act
+ bool equal = a.Equals(b);
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void EqualityOperator_StringEnumAndStringEnum_ReturnsTrue()
+ {
+ // Arrange
+ var a = TestStringEnum.Auto;
+ var b = TestStringEnum.Auto; // different instance, same inner value
+
+ // Act
+ bool equal = a == b;
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void EqualityOperator_StringEnumAndNull_ReturnsFalse()
+ {
+ // Arrange
+ var a = TestStringEnum.Custom(string.Empty);
+
+ // Act
+ bool equal = a == null;
+
+ // Assert
+ Assert.False(equal);
+ }
+
+ [Fact]
+ public void EqualityOperator_NullAndNull_ReturnsTrue()
+ {
+ // Arrange
+ TestStringEnum a = null;
+
+ // Act
+ bool equal = a == null;
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void EqualityOperator_SameReference_ReturnsTrue()
+ {
+ // Arrange
+ var a = TestStringEnum.Custom(string.Empty);
+ var b = a;
+
+ // Act
+ bool equal = a == b;
+
+ // Assert
+ Assert.True(equal);
+ }
+
+ [Fact]
+ public void GetHashCode_InnerValueAndEnum_Equals()
+ {
+ // Arrange
+ const string ExampleString = "asdf";
+ TestStringEnum stringEnum = TestStringEnum.Custom(ExampleString);
+
+ // Act
+ int hashCodeValue = ExampleString.GetHashCode();
+ int hashCodeEnum = stringEnum.GetHashCode();
+
+ // Assert
+ Assert.Equal(hashCodeValue, hashCodeEnum);
+ }
+
+ [Fact]
+ public void GetHashCode_EnumAndEnum_Equals()
+ {
+ // Arrange
+ var a = TestStringEnum.Auto;
+ var b = TestStringEnum.Auto; // different instance, same inner value
+
+ // Act
+ int hashA = a.GetHashCode();
+ int hashB = b.GetHashCode();
+
+ // Assert
+ Assert.Equal(hashA, hashB);
+ }
+ }
+}
diff --git a/ChartJs.Blazor.Tests/StringEnumTests.General.cs b/ChartJs.Blazor.Tests/StringEnumTests.General.cs
new file mode 100644
index 00000000..3a54edfb
--- /dev/null
+++ b/ChartJs.Blazor.Tests/StringEnumTests.General.cs
@@ -0,0 +1,17 @@
+using ChartJs.Blazor.ChartJS.Common.Enums;
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+using Xunit;
+
+namespace ChartJs.Blazor.Tests
+{
+ public partial class StringEnumTests
+ {
+ [Fact]
+ public void Construct_NullValue_ThrowsArgumentNullException()
+ {
+ Assert.Throws(() => TestStringEnum.Custom(null));
+ }
+ }
+}
diff --git a/ChartJs.Blazor.Tests/StringEnumTests.Serialization.cs b/ChartJs.Blazor.Tests/StringEnumTests.Serialization.cs
new file mode 100644
index 00000000..aa06e268
--- /dev/null
+++ b/ChartJs.Blazor.Tests/StringEnumTests.Serialization.cs
@@ -0,0 +1,33 @@
+using ChartJs.Blazor.ChartJS.Common.Enums;
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+using Xunit;
+
+namespace ChartJs.Blazor.Tests
+{
+ public partial class StringEnumTests
+ {
+ [Theory]
+ [InlineData("foo")]
+ [InlineData("bar")]
+ [InlineData("1234567890")]
+ [InlineData("")]
+ [InlineData("whomst'd've'ly'yaint'nt'ed'ies's'y'es")]
+ [InlineData("\"That's what!\", she said.")]
+ [InlineData("\uD83D\uDE42 emoji shenanigans")]
+ [InlineData("¨öä$ü¨^'{}][\\|/-.,+-/*")]
+ public void Serialize_StringEnum_AsRoot(string value)
+ {
+ // Arrange
+ TestStringEnum objEnum = TestStringEnum.Custom(value);
+ string escapedValue = JsonConvert.ToString(value);
+
+ // Act
+ string serialized = JsonConvert.SerializeObject(objEnum);
+
+ // Assert
+ Assert.Equal(escapedValue, serialized);
+ }
+ }
+}
diff --git a/ChartJs.Blazor.Tests/StringEnumTests.TestClasses.cs b/ChartJs.Blazor.Tests/StringEnumTests.TestClasses.cs
new file mode 100644
index 00000000..3a72d242
--- /dev/null
+++ b/ChartJs.Blazor.Tests/StringEnumTests.TestClasses.cs
@@ -0,0 +1,19 @@
+using ChartJs.Blazor.ChartJS.Common.Enums;
+using Newtonsoft.Json;
+using System;
+using System.Globalization;
+using Xunit;
+
+namespace ChartJs.Blazor.Tests
+{
+ public partial class StringEnumTests
+ {
+ private class TestStringEnum : StringEnum
+ {
+ public static TestStringEnum Auto => new TestStringEnum("auto");
+ public static TestStringEnum Custom(string value) => new TestStringEnum(value);
+
+ private TestStringEnum(string stringRep) : base(stringRep) { }
+ }
+ }
+}
diff --git a/ChartJs.Blazor.sln b/ChartJs.Blazor.sln
index 3f0c1a90..e80e1d45 100644
--- a/ChartJs.Blazor.sln
+++ b/ChartJs.Blazor.sln
@@ -7,7 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{08E01108-AF6
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5474F748-8E27-4FD6-AD9D-1087578ED4D7}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChartJs.Blazor", "src\ChartJs.Blazor\ChartJs.Blazor.csproj", "{1990A3D7-7B00-469F-BC97-F614393F7A52}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChartJs.Blazor", "src\ChartJs.Blazor\ChartJs.Blazor.csproj", "{1990A3D7-7B00-469F-BC97-F614393F7A52}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ClientSide", "ClientSide", "{474E0BD0-B8C0-4A00-9B05-B1B33F3B7C94}"
EndProject
@@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChartJs.Blazor.Sample.Serve
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChartJs.Blazor.Sample.ClientSide", "samples\ClientSide\ChartJs.Blazor.Sample.ClientSide\ChartJs.Blazor.Sample.ClientSide.csproj", "{153F4719-305A-4AD0-975A-91ABD9935F87}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChartJs.Blazor.Tests", "ChartJs.Blazor.Tests\ChartJs.Blazor.Tests.csproj", "{135A9451-FC82-48B4-89F6-3CBF5312B2C2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -43,6 +45,10 @@ Global
{153F4719-305A-4AD0-975A-91ABD9935F87}.Debug|Any CPU.Build.0 = Debug|Any CPU
{153F4719-305A-4AD0-975A-91ABD9935F87}.Release|Any CPU.ActiveCfg = Release|Any CPU
{153F4719-305A-4AD0-975A-91ABD9935F87}.Release|Any CPU.Build.0 = Release|Any CPU
+ {135A9451-FC82-48B4-89F6-3CBF5312B2C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {135A9451-FC82-48B4-89F6-3CBF5312B2C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {135A9451-FC82-48B4-89F6-3CBF5312B2C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {135A9451-FC82-48B4-89F6-3CBF5312B2C2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -55,6 +61,7 @@ Global
{3FB87C6F-5C21-4095-AC64-35AE6299B88F} = {99703BE7-7A02-454D-8066-87BBEBA039BD}
{D547792A-5B2C-4FAF-93AA-71BAA9BF845C} = {A9B59800-FB77-43FE-BF42-BE991D9FDBF5}
{153F4719-305A-4AD0-975A-91ABD9935F87} = {474E0BD0-B8C0-4A00-9B05-B1B33F3B7C94}
+ {135A9451-FC82-48B4-89F6-3CBF5312B2C2} = {08E01108-AF62-4E37-824D-0A22DA1988C3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6B9B5C87-8AF0-4846-81C5-798F5C654FB6}
diff --git a/src/ChartJs.Blazor/ChartJS/Common/Enums/JsonConverter/JsonObjectEnumConverter.cs b/src/ChartJs.Blazor/ChartJS/Common/Enums/JsonConverter/JsonObjectEnumConverter.cs
deleted file mode 100644
index 5ffb3420..00000000
--- a/src/ChartJs.Blazor/ChartJS/Common/Enums/JsonConverter/JsonObjectEnumConverter.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Newtonsoft.Json;
-
-namespace ChartJs.Blazor.ChartJS.Common.Enums.JsonConverter
-{
- ///
- /// JsonConverter for converting and writing an ObjectEnum value. This JsonConverter can only write.
- ///
- internal class JsonObjectEnumConverter : JsonWriteOnlyConverter
- {
- public override void WriteJson(JsonWriter writer, ObjectEnum wrapper, JsonSerializer serializer)
- {
- try
- {
- // if it can be written in a single JToken,
- // json.net understands what type the wrapped object (.Value) is and serializes it accordingly -> correct value and type (eg. bool, string, double)
- writer.WriteValue(wrapper.Value);
- }
- catch (JsonWriterException)
- {
- // if there was an error, try to explicitly serialize it before writing
- // if this also fails just let it bubble up because the developer should not have values in their enum that fail here
- serializer.Serialize(writer, wrapper.Value);
- }
- }
- }
-}
diff --git a/src/ChartJs.Blazor/ChartJS/Common/Enums/JsonConverter/JsonStringEnumConverter.cs b/src/ChartJs.Blazor/ChartJS/Common/Enums/JsonConverter/JsonStringEnumConverter.cs
deleted file mode 100644
index 5f9c4e94..00000000
--- a/src/ChartJs.Blazor/ChartJS/Common/Enums/JsonConverter/JsonStringEnumConverter.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Newtonsoft.Json;
-
-namespace ChartJs.Blazor.ChartJS.Common.Enums.JsonConverter
-{
- ///
- /// JsonConverter for converting and writing a StringEnum value. This JsonConverter can only write.
- ///
- internal class JsonStringEnumConverter : JsonWriteOnlyConverter
- {
- public override void WriteJson(JsonWriter writer, StringEnum value, JsonSerializer serializer)
- {
- // ToString was overwritten by StringEnum -> safe to just print the string representation
- writer.WriteValue(value.ToString());
- }
- }
-}
diff --git a/src/ChartJs.Blazor/ChartJS/Common/Enums/ObjectEnum.cs b/src/ChartJs.Blazor/ChartJS/Common/Enums/ObjectEnum.cs
index 74297339..ae087db8 100644
--- a/src/ChartJs.Blazor/ChartJS/Common/Enums/ObjectEnum.cs
+++ b/src/ChartJs.Blazor/ChartJS/Common/Enums/ObjectEnum.cs
@@ -1,53 +1,125 @@
-using Newtonsoft.Json;
-using ChartJs.Blazor.ChartJS.Common.Enums.JsonConverter;
+using System;
+using System.Collections.Generic;
+using System.Linq;
namespace ChartJs.Blazor.ChartJS.Common.Enums
{
///
- /// Inherit this class if you are in need of a pseudo-Enum which can hold values of different kinds (eg. string, double and bool).
+ /// The base class for enums that can represent different types. We also use these
+ /// "enums" like Discriminated Unions to provide a type safe way of communicating with the
+ /// dynamic language JavaScript is.
+ ///
+ /// De-/serialization is supported but only for the following types:
+ /// , , and .
+ /// For the deserialization, the constructors with a single parameter of a supported type
+ /// are considered for instantiating the object enum.
+ ///
+ ///
+ /// When implementing an object enum, make sure to provide only private constructors
+ /// with the types that are allowed (DO NOT expose public constructors; expose meaningful
+ /// static factory methods instead). The actual enum values are static properties that pass
+ /// the correct value to the private constructor. Make these properties return new values
+ /// everytime so we don't create all the enum values even though we don't use them.
+ /// In the classic use case, we don't call many of these properties anyway and usually
+ /// only a few times. You can also have static factory methods that
+ /// create an instance of the object enum with the specified value as long as the parameter
+ /// type is supported. Also consider sealing your enum unless you have a specific reason not to.
+ ///
///
- [JsonConverter(typeof(JsonObjectEnumConverter))]
- public abstract class ObjectEnum
+ [Newtonsoft.Json.JsonConverter(typeof(Serialization.JsonObjectEnumConverter))]
+ public abstract class ObjectEnum : IEquatable
{
+ ///
+ /// Gets the s that are supported for serialization and deserialization.
+ /// can contain objects of different types but you will get a
+ /// once you try to serialize (or deserialize) that
+ /// .
+ ///
+ private static readonly Type[] SupportedSerializationTypes = new[]
+ {
+ typeof(int), typeof(double), typeof(string), typeof(bool)
+ };
+
///
/// Holds the actual value represented by this instance.
///
internal object Value { get; }
-
+
///
- /// Creates a new instance of with a value.
+ /// Creates a new instance of .
///
- /// The value this enum-instance is supposed to represent.
- protected ObjectEnum(object value) => Value = value;
+ /// The value this instance is supposed to represent.
+ protected ObjectEnum(object value)
+ {
+ Value = value ?? throw new ArgumentNullException(nameof(value));
+
+ if (value is ObjectEnum)
+ throw new ArgumentException("The value cannot be an ObjectEnum. " +
+ "Recursive ObjectEnums aren't allowed.");
+ }
///
- /// Returns the string representation of the underlying object. Calls .ToString().
+ /// Checks if a is in the list of supported serialization types.
+ /// If this function returns , de-/serialization will fail on
+ /// s containing an instance of that
+ /// ().
///
- /// The string representation of the underlying object.
- public override string ToString() => Value.ToString();
-
-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
- public static bool operator == (ObjectEnum a, ObjectEnum b) => a.Value == b.Value;
- public static bool operator != (ObjectEnum a, ObjectEnum b) => a.Value != b.Value;
-#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
+ /// The to check.
+ internal static bool IsSupportedSerializationType(Type type) =>
+ SupportedSerializationTypes.Contains(type);
///
- /// Determines whether the specified object instance is considered equal to the current instance.
+ /// Determines whether the specified object is considered equal to the current object.
+ ///
+ /// is considered to be equal to this instance if it..
+ ///
+ /// - is the same instance as this instance.
+ /// - is another with the same internal value.
+ /// - is the same value as the internal value of this .
+ ///
+ ///
///
- /// The object to compare with.
- /// true if the objects are considered equal; otherwise, false.
+ /// The object to compare with the current object.
+ /// true if the specified object is considered to be equal to the current object;
+ /// otherwise, false.
public override bool Equals(object obj)
{
- if (typeof(ObjectEnum).IsAssignableFrom(obj.GetType()) && obj != null) return Value.Equals(((ObjectEnum)obj).Value);
+ if (obj is ObjectEnum asEnum)
+ {
+ return Equals(asEnum);
+ }
- // it also counts as equal if the object to compare is equal to the object stored in the wrapper
return Value.Equals(obj);
}
///
- /// Returns the hash of the underlying object.
+ /// Indicates whether the current object is equal to another object of the same type.
///
- /// The hash of the underlying object.
+ /// An object to compare with this object.
+ /// true if the current object is equal to the other parameter; otherwise, false.
+ ///
+ public bool Equals(ObjectEnum other) =>
+ other != null &&
+ Value.Equals(other.Value);
+
+ ///
+ /// Returns the hash code of the underlying value.
+ ///
+ /// The hash code of the underlying value.
public override int GetHashCode() => Value.GetHashCode();
+
+ ///
+ /// Returns the representation of the underlying object.
+ /// Calls on the underlying object.
+ ///
+ /// The representation of the underlying object.
+ public override string ToString() => Value.ToString();
+
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+ public static bool operator ==(ObjectEnum left, ObjectEnum right) =>
+ EqualityComparer