mirror of
https://gitee.com/LongbowEnterprise/BootstrapBlazor.git
synced 2024-12-05 13:39:39 +08:00
!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:
parent
0e1b866955
commit
7516cb7da7
@ -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;
|
||||
}));
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
189
test/UnitTest/Components/DisplayTest.cs
Normal file
189
test/UnitTest/Components/DisplayTest.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user