!2377 test(#I4SL47): add unit test for Display

* feat: Display 组件增加 TypeResolver 参数
* test: 增加可为空集合内部有空值单元测试
* test: 增加 TypeResolver 单元测试
* test: 增加泛型空方法单元测试
* test: 增加标签单元测试
* test: 增加空类型单元测试
* test: 增加 DateTime 类型单元测试
* test: 增加字符串值单元测试
* test: 增加泛型单元测试
* test: 增加集合单元测试
* test: 增加数组类型单元测试
* test: 增加枚举类型 单元测试
* test: 增加 Formatter 单元测试
* test: 增加 Display 单元测试
* refactor: 精简 Display 代码
* refactor: DisplayBase 移除宏定义
* refactor: AutoFill 单元测试消除警告信息
This commit is contained in:
Argo 2022-02-02 05:09:33 +00:00
parent 0e1b866955
commit 7516cb7da7
4 changed files with 239 additions and 41 deletions

View File

@ -45,6 +45,13 @@ public partial class Display<TValue>
[Parameter]
public IEnumerable<SelectedItem>? Lookup { get; set; }
/// <summary>
/// 获得/设置 类型解析回调方法 组件泛型为 Array 时内部调用
/// </summary>
[Parameter]
public Func<Assembly?, string, bool, Type?>? TypeResolver { get; set; }
/// <summary>
/// OnParametersSetAsync 方法
/// </summary>
@ -106,31 +113,45 @@ public partial class Display<TValue>
return ret ?? valueString ?? string.Empty;
}
private static Func<TValue, string>? _converterArray;
private Func<TValue, string>? _converterArray;
/// <summary>
/// 获取属性方法 Lambda 表达式
/// </summary>
/// <returns></returns>
private static string ConvertArrayToString(TValue value)
private string ConvertArrayToString(TValue value)
{
return (_converterArray ??= ConvertArrayToStringLambda())(value);
static Func<TValue, string> ConvertArrayToStringLambda()
Func<TValue, string> ConvertArrayToStringLambda()
{
Func<TValue, string> ret = _ => "";
var param_p1 = Expression.Parameter(typeof(Array));
var target_type = typeof(TValue).UnderlyingSystemType;
var methodType = Type.GetType(target_type.FullName!.Replace("[]", ""));
var methodType = ResolveArrayType();
if (methodType != null)
{
var method = typeof(string).GetMethods().Where(m => m.Name == "Join" && m.IsGenericMethod && m.GetParameters()[0].ParameterType == typeof(string)).FirstOrDefault()?.MakeGenericMethod(methodType);
if (method != null)
{
var body = Expression.Call(method, Expression.Constant(","), Expression.Convert(param_p1, target_type));
ret = Expression.Lambda<Func<TValue, string>>(body, param_p1).Compile();
}
// 调用 string.Join<T>(",", IEnumerable<T>) 方法
var method = typeof(string).GetMethods().Where(m => m.Name == "Join" && m.IsGenericMethod && m.GetParameters()[0].ParameterType == typeof(string)).First().MakeGenericMethod(methodType);
var body = Expression.Call(method, Expression.Constant(","), Expression.Convert(param_p1, target_type));
ret = Expression.Lambda<Func<TValue, string>>(body, param_p1).Compile();
}
return ret;
Type? ResolveArrayType()
{
Type? ret = null;
var typeName = target_type.FullName;
if (!string.IsNullOrEmpty(typeName))
{
typeName = typeName.Replace("[]", "");
if (typeName.Contains("+"))
{
typeName = typeName.Split('+', StringSplitOptions.RemoveEmptyEntries).Last();
}
ret = Type.GetType(typeName, null, TypeResolver, false, true);
}
return ret;
}
}
}
@ -148,47 +169,35 @@ public partial class Display<TValue>
static Func<TValue, string> ConvertEnumerableToStringLambda()
{
Func<TValue, string> ret = _ => "";
var typeArguments = typeof(TValue).GenericTypeArguments;
var param_p1 = Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(typeArguments));
var method = typeof(string).GetMethods().Where(m => m.Name == "Join" && m.IsGenericMethod && m.GetParameters()[0].ParameterType == typeof(string)).FirstOrDefault()?.MakeGenericMethod(typeArguments);
if (method != null)
{
var body = Expression.Call(method, Expression.Constant(","), param_p1);
ret = Expression.Lambda<Func<TValue, string>>(body, param_p1).Compile();
}
return ret;
var method = typeof(string).GetMethods().Where(m => m.Name == "Join" && m.IsGenericMethod && m.GetParameters()[0].ParameterType == typeof(string)).First().MakeGenericMethod(typeArguments);
var body = Expression.Call(method, Expression.Constant(","), param_p1);
return Expression.Lambda<Func<TValue, string>>(body, param_p1).Compile();
}
static Func<TValue, IEnumerable<string>> ConvertToEnumerableStringLambda()
{
Func<TValue, IEnumerable<string>> ret = _ => Enumerable.Empty<string>();
var typeArguments = typeof(TValue).GenericTypeArguments;
var param_p1 = Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(typeArguments));
var method = typeof(Display<>).MakeGenericType(typeof(TValue))
.GetMethod("Cast", BindingFlags.NonPublic | BindingFlags.Static)?
.GetMethod(nameof(Cast), BindingFlags.NonPublic | BindingFlags.Static)!
.MakeGenericMethod(typeArguments);
if (method != null)
{
var body = Expression.Call(method, param_p1);
ret = Expression.Lambda<Func<TValue, IEnumerable<string>>>(body, param_p1).Compile();
}
return ret;
var body = Expression.Call(method, param_p1);
return Expression.Lambda<Func<TValue, IEnumerable<string>>>(body, param_p1).Compile();
}
}
private static IEnumerable<string?> Cast<TType>(IEnumerable<TType> source) => source.Select(i => i?.ToString());
private static IEnumerable<string> Cast<TType>(IEnumerable<TType> source) => source.Select(i => i?.ToString() ?? string.Empty);
private string GetTextByValue(IEnumerable<string> source) => Lookup == null
? ""
: string.Join(",", source.Aggregate(new List<string>(), (s, i) =>
private string GetTextByValue(IEnumerable<string> source) => string.Join(",", source.Aggregate(new List<string>(), (s, i) =>
{
var text = Lookup!.FirstOrDefault(d => d.Value.Equals(i, StringComparison.OrdinalIgnoreCase))?.Text;
if (text != null)
{
var text = Lookup.FirstOrDefault(d => d.Value.Equals(i, StringComparison.OrdinalIgnoreCase))?.Text;
if (text != null)
{
s.Add(text);
}
return s;
}));
s.Add(text);
}
return s;
}));
}

View File

@ -5,6 +5,7 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Threading.Tasks;
@ -30,7 +31,6 @@ public abstract class DisplayBase<TValue> : TooltipComponentBase
/// </summary>
protected Type? NullableUnderlyingType { get; set; }
#nullable disable
/// <summary>
/// Gets or sets the value of the input. This should be used with two-way binding.
/// </summary>
@ -38,8 +38,8 @@ public abstract class DisplayBase<TValue> : TooltipComponentBase
/// @bind-Value="model.PropertyName"
/// </example>
[Parameter]
public TValue Value { get; set; }
#nullable restore
[NotNull]
public TValue? Value { get; set; }
/// <summary>
/// Gets or sets a callback that updates the bound value.

View File

@ -193,7 +193,7 @@ public class AutoFillTest : BootstrapBlazorTestBase
pb.Add(a => a.Template, v => builder =>
{
builder.OpenElement(0, "div");
builder.AddContent(1, v.Value);
builder.AddContent(1, v?.Value);
builder.CloseElement();
});
});

View File

@ -0,0 +1,189 @@
// 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;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Web;
namespace UnitTest.Components;
public class DisplayTest : BootstrapBlazorTestBase
{
[Fact]
public void FormatterAsync_Ok()
{
var cut = Context.RenderComponent<Display<string>>(pb =>
{
pb.Add(a => a.FormatterAsync, new Func<string, Task<string>>(v =>
{
return Task.FromResult("FormattedValue");
}));
});
Assert.Contains("FormattedValue", cut.Markup);
}
[Fact]
public void EnumValue_Ok()
{
var cut = Context.RenderComponent<Display<EnumEducation>>(pb =>
{
pb.Add(a => a.Value, EnumEducation.Primary);
});
Assert.Contains("小学", HttpUtility.HtmlDecode(cut.Markup));
}
[Fact]
public void ArrayValue_Ok()
{
var cut = Context.RenderComponent<Display<byte[]>>(pb =>
{
pb.Add(a => a.Value, new byte[] { 0x01, 0x12, 0x34, 0x56 });
});
Assert.Contains("1,18,52,86", cut.Markup);
}
[Fact]
public void TypeResolver_Ok()
{
var cut = Context.RenderComponent<Display<DisplayTest.Foo[]>>(pb =>
{
pb.Add(a => a.Value, new DisplayTest.Foo[] { new DisplayTest.Foo() { Value = "1" } });
pb.Add(a => a.TypeResolver, new Func<Assembly, string, bool, Type>((assembly, typeName, ignoreCase) => typeof(DisplayTest.Foo)));
});
Assert.Equal("<div class=\"form-control is-display\">1</div>", cut.Markup);
}
[Fact]
public void TypeResolver_Null()
{
var cut = Context.RenderComponent<Display<DisplayTest.Foo[]>>(pb =>
{
pb.Add(a => a.Value, new DisplayTest.Foo[] { new DisplayTest.Foo() { Value = "1" } });
});
Assert.Equal("<div class=\"form-control is-display\"></div>", cut.Markup);
}
[Fact]
public void EnumerableNullValue_Ok()
{
var cut = Context.RenderComponent<Display<List<int?>>>(pb =>
{
pb.Add(a => a.Value, new List<int?> { 1, 2, 3, 4, null });
pb.Add(a => a.Lookup, new List<SelectedItem>()
{
new("", "Test"),
new("1", "Test 1")
});
});
// 给定值中有空值Lookup 中对空值转化为 Test
Assert.Equal("<div class=\"form-control is-display\">Test 1,Test</div>", cut.Markup);
}
[Fact]
public void EnumerableValue_Ok()
{
var cut = Context.RenderComponent<Display<List<int>>>(pb =>
{
pb.Add(a => a.Value, new List<int> { 1, 2, 3, 4 });
});
Assert.Contains("1,2,3,4", cut.Markup);
cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.Lookup, new List<SelectedItem>()
{
new("1", "Test 1")
});
});
Assert.Contains("Test 1", cut.Markup);
}
[Fact]
public void GenericValue_Ok()
{
var cut = Context.RenderComponent<Display<DisplayGenericValueMock<string>>>(pb =>
{
pb.Add(a => a.Value, new DisplayGenericValueMock<string>() { Value = "1" });
pb.Add(a => a.Lookup, new List<SelectedItem>()
{
new("1", "Test 1")
});
});
Assert.Contains("Test 1", cut.Markup);
}
[Fact]
public void StringValue_Ok()
{
var cut = Context.RenderComponent<Display<string>>(pb =>
{
pb.Add(a => a.Value, "Test 1");
});
Assert.Contains("Test 1", cut.Markup);
}
[Fact]
public void DateTimeValue_Ok()
{
var cut = Context.RenderComponent<Display<DateTime>>(pb =>
{
pb.Add(a => a.Value, DateTime.Now);
pb.Add(a => a.FormatString, "yyyy-MM-dd");
});
Assert.Contains($"{DateTime.Now:yyyy-MM-dd}", cut.Markup);
}
[Fact]
public void NullValue_Ok()
{
var cut = Context.RenderComponent<Display<string?>>(pb =>
{
pb.Add(a => a.Value, null);
pb.Add(a => a.Lookup, new List<SelectedItem>()
{
new("1", "Test 1")
});
});
Assert.Equal("<div class=\"form-control is-display\"></div>", cut.Markup);
}
[Fact]
public void ShowLabel_Ok()
{
var cut = Context.RenderComponent<Display<string>>(pb =>
{
pb.Add(a => a.ShowLabel, true);
pb.Add(a => a.DisplayText, "Test Label");
});
Assert.Contains("Test Label", cut.Markup);
}
class DisplayGenericValueMock<T>
{
[NotNull]
public T? Value { get; set; }
public override string? ToString()
{
return Value.ToString();
}
}
class Foo
{
public string Value { get; set; } = "";
public override string ToString()
{
return Value.ToString();
}
}
}