diff --git a/src/BootstrapBlazor/Extensions/ObjectExtensions.cs b/src/BootstrapBlazor/Extensions/ObjectExtensions.cs index 76864758ebcd69b8c9fa5d05cf9029321697fa64..6902ee8a10c31f06b1758041d483194bb5752fe4 100644 --- a/src/BootstrapBlazor/Extensions/ObjectExtensions.cs +++ b/src/BootstrapBlazor/Extensions/ObjectExtensions.cs @@ -113,7 +113,7 @@ public static class ObjectExtensions else { var methodInfo = typeof(ObjectExtensions).GetMethods().FirstOrDefault(m => m.IsGenericMethod)!.MakeGenericMethod(type); - var v = type == typeof(string) ? null : Activator.CreateInstance(type); + var v = Activator.CreateInstance(type); var args = new object?[] { source, v }; ret = (bool)methodInfo.Invoke(null, args)!; val = ret ? args[1] : null; @@ -170,7 +170,7 @@ public static class ObjectExtensions /// /// /// - internal static string ToFileSizeString(this long fileSize) => fileSize switch + public static string ToFileSizeString(this long fileSize) => fileSize switch { >= 1024 and < 1024 * 1024 => $"{Math.Round(fileSize / 1024D, 0, MidpointRounding.AwayFromZero)} KB", >= 1024 * 1024 and < 1024 * 1024 * 1024 => $"{Math.Round(fileSize / 1024 / 1024D, 0, MidpointRounding.AwayFromZero)} MB", @@ -202,14 +202,13 @@ public static class ObjectExtensions { var fieldName = col.GetFieldName(); var canWrite = IsDynamicObject(); - return canWrite || (fieldName.Contains('.') - ? modelType.GetPropertyByName(fieldName)?.CanWrite ?? false - : ComplexCanWrite()); + return canWrite || ComplexCanWrite(); bool IsDynamicObject() => modelType == typeof(DynamicObject); bool ComplexCanWrite() { + var ret = false; var propertyNames = fieldName.Split('.'); PropertyInfo? propertyInfo = null; Type? propertyType = null; @@ -226,7 +225,11 @@ public static class ObjectExtensions propertyType = propertyInfo.PropertyType; } } - return propertyInfo?.CanWrite ?? false; + if (propertyInfo != null) + { + ret = propertyInfo.CanWrite; + } + return ret; } } } diff --git a/test/UnitTest/Extensions/ObjectExtensionsTest.cs b/test/UnitTest/Extensions/ObjectExtensionsTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..b7eb4c395d0cd635395956ed7fc46ae33bce6d3d --- /dev/null +++ b/test/UnitTest/Extensions/ObjectExtensionsTest.cs @@ -0,0 +1,262 @@ +// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Website: https://www.blazor.zone or https://argozhang.github.io/ + +using BootstrapBlazor.Shared; +using System.ComponentModel; +using System.Globalization; + +namespace UnitTest.Extensions; + +public class ObjectExtensionsTest +{ + [Theory] + [InlineData(null, "")] + [InlineData("95%", "95%")] + [InlineData("95px", "95px")] + [InlineData("95", "95px")] + [InlineData("test", "")] + public static void ConvertToPercentString_Ok(string? source, string expect) + { + var actual = source.ConvertToPercentString(); + Assert.Equal(expect, actual); + } + + [Theory] + [InlineData(typeof(int?), true)] + [InlineData(typeof(long?), true)] + [InlineData(typeof(float?), true)] + [InlineData(typeof(short?), true)] + [InlineData(typeof(double?), true)] + [InlineData(typeof(decimal?), true)] + [InlineData(typeof(int), true)] + [InlineData(typeof(long), true)] + [InlineData(typeof(float), true)] + [InlineData(typeof(short), true)] + [InlineData(typeof(double), true)] + [InlineData(typeof(decimal), true)] + [InlineData(typeof(DateTime?), false)] + [InlineData(typeof(DateTime), false)] + [InlineData(typeof(string), false)] + public static void IsNumber_Ok(Type source, bool expect) + { + var actual = source.IsNumber(); + Assert.Equal(expect, actual); + } + + [Theory] + [InlineData(typeof(DateTime?), true)] + [InlineData(typeof(DateTime), true)] + [InlineData(typeof(DateTimeOffset?), true)] + [InlineData(typeof(DateTimeOffset), true)] + [InlineData(typeof(string), false)] + public static void IsDateTime_Ok(Type source, bool expect) + { + var actual = source.IsDateTime(); + Assert.Equal(expect, actual); + } + + [Theory] + [InlineData(typeof(SortOrder), "枚举")] + [InlineData(typeof(int), "数字")] + [InlineData(typeof(DateTimeOffset), "日期")] + [InlineData(typeof(string), "字符串")] + [InlineData(typeof(Foo), "字符串")] + public static void GetTypeDesc_Ok(Type source, string expect) + { + var actual = source.GetTypeDesc(); + Assert.Equal(expect, actual); + } + + [Fact] + public static void TryConvertTo_Ok() + { + var source = "test"; + var result = source.TryConvertTo(typeof(string), out var v); + Assert.True(result); + Assert.Equal(source, v); + + source = "123"; + result = source.TryConvertTo(typeof(int), out var i); + Assert.True(result); + Assert.Equal(123, i); + + source = "123"; + result = source.TryConvertTo(typeof(DateTime), out var d); + Assert.False(result); + } + + [Fact] + public static void TryConvertTo_Generic() + { + var source = "123"; + var result = source.TryConvertTo(out var v); + Assert.True(result); + Assert.Equal(123, v); + + source = null; + result = source.TryConvertTo(out var s); + Assert.True(result); + Assert.Null(s); + + result = source.TryConvertTo(out var i); + Assert.True(result); + Assert.Equal(0, i); + + source = ""; + result = source.TryConvertTo(out var e); + Assert.False(result); + + source = "False"; + result = source.TryConvertTo(out var b1); + Assert.True(result); + Assert.False(b1); + + source = "false"; + result = source.TryConvertTo(out var b2); + Assert.True(result); + Assert.False(b2); + + source = "test"; + result = source.TryConvertTo(out var dt); + Assert.False(result); + + source = typeof(Foo).Name; + result = source.TryConvertTo(out var f); + Assert.False(result); + + source = typeof(Dummy).FullName; + result = source.TryConvertTo(out var _); + Assert.True(result); + } + + [Theory] + [InlineData(100f, "100 B")] + [InlineData(1024f, "1 KB")] + [InlineData(1024 * 1024f, "1 MB")] + [InlineData(1024 * 1024 * 1024f, "1 GB")] + public void ToFileSizeString_Ok(long source, string expect) + { + var actual = source.ToFileSizeString(); + Assert.Equal(expect, actual); + } + + [Theory] + [InlineData(ItemChangedType.Add)] + [InlineData(ItemChangedType.Update)] + public void IsEditable_Editable(ItemChangedType itemChangedType) + { + var editorItem = new EditorItem(); + Assert.True(editorItem.IsEditable(itemChangedType)); + } + + [Theory] + [InlineData(ItemChangedType.Add)] + [InlineData(ItemChangedType.Update)] + public void IsEditable_Readonly(ItemChangedType itemChangedType) + { + var editorItem = new EditorItem(); + editorItem.SetParametersAsync(ParameterView.FromDictionary(new Dictionary + { + ["Readonly"] = true + })); + Assert.False(editorItem.IsEditable(itemChangedType)); + } + + [Theory] + [InlineData(ItemChangedType.Add, true)] + [InlineData(ItemChangedType.Add, false)] + public void IsEditable_IsReadonlyWhenAdd(ItemChangedType itemChangedType, bool val) + { + var editorItem = new EditorItem() + { + IsReadonlyWhenAdd = val + }; + Assert.Equal(val, !editorItem.IsEditable(itemChangedType)); + } + + [Theory] + [InlineData(ItemChangedType.Update, true)] + [InlineData(ItemChangedType.Update, false)] + public void IsEditable_IsReadonlyWhenEdit(ItemChangedType itemChangedType, bool val) + { + var editorItem = new EditorItem() + { + IsReadonlyWhenEdit = val + }; + Assert.Equal(val, !editorItem.IsEditable(itemChangedType)); + } + + [Theory] + [InlineData(ItemChangedType.Add)] + [InlineData(ItemChangedType.Update)] + public void IsEditable_Search(ItemChangedType itemChangedType) + { + var editorItem = new EditorItem(); + editorItem.SetParametersAsync(ParameterView.FromDictionary(new Dictionary + { + ["Editable"] = false + })); + Assert.True(editorItem.IsEditable(itemChangedType, true)); + } + + [Fact] + public void CanWrite_Ok() + { + var item = new MockEditItem() { FieldName = "Name" }; + var result = item.CanWrite(typeof(Foo)); + Assert.True(result); + + var item2 = new MockEditItem() { FieldName = "Foo.Name" }; + result = item2.CanWrite(typeof(Dummy)); + Assert.True(result); + + item2 = new MockEditItem() { FieldName = "Count" }; + result = item2.CanWrite(typeof(Dummy)); + Assert.False(result); + + // DynamicObject always return True + Assert.True(item2.CanWrite(typeof(DynamicObject))); + } + + [Theory] + [InlineData("Test")] + [InlineData("Foo.Test")] + public void CanWrite_Exception(string fieldName) + { + var item = new MockEditItem() { FieldName = fieldName }; + Assert.Throws(() => item.CanWrite(typeof(Dummy))); + } + + [TypeConverter(typeof(DummyConverter))] + private class Dummy + { + public string? Name { get; set; } + + public Foo Foo { get; set; } = new Foo(); + + public int Count { get; } + } + + private class DummyConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + { + return true; + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + return new Dummy(); + } + } + + private class MockEditItem : EditorItem, IEditorItem + { + public string? FieldName { get; set; } + + string IEditorItem.GetFieldName() => FieldName!; + + public Dummy Dummy { get; set; } = new Dummy(); + } +} diff --git a/test/UnitTest/Extensions/UtilityTest.cs b/test/UnitTest/Extensions/UtilityTest.cs deleted file mode 100644 index 907e222674316dee43908e8d72fb69ce2ce96f8b..0000000000000000000000000000000000000000 --- a/test/UnitTest/Extensions/UtilityTest.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Argo Zhang (argo@163.com). All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Website: https://www.blazor.zone or https://argozhang.github.io/ - -using BootstrapBlazor.Shared; - -namespace UnitTest.Extensions; - -public class UtilityTest : BootstrapBlazorTestBase -{ - [Fact] - public void GetKeyValue_Ok() - { - var foo = new Foo() { Id = 1 }; - var v = Utility.GetKeyValue(foo); - Assert.Equal(1, v); - - object foo1 = new Foo() { Id = 2 }; - v = Utility.GetKeyValue(foo1); - Assert.Equal(2, v); - } -}