!2475 test(#I4VXYM): add unit test for BootstrapInputNumber

* test: 增加类型异常单元测试
* test: 增加验证错误信息单元测试
* test: 增加数据类型单元测试
* revert: 恢复单元测试
* test: 增加数据类型单元测试
* test: 增加 ShowButtons 单元测试
* test: 增加 Blur 单元测试
* doc: 更新文档
* Merge branch 'main' into test-input
* test: 增加表单验证单元测试
* test: 增加 int 单元测试
* test: 增加 double 单元测试
* test: 增加单元测试
* refactor: 移除静态构造函数
* doc: 格式化代码
This commit is contained in:
Argo 2022-03-02 06:20:44 +00:00
parent 49c26a9a6f
commit 191023fb5e
4 changed files with 232 additions and 47 deletions

View File

@ -23,34 +23,38 @@ public sealed partial class Popovers
/// <returns></returns>
private static IEnumerable<AttributeItem> GetAttributes() => new AttributeItem[]
{
// TODO: 移动到数据库中
new AttributeItem() {
Name = "Cotent",
Description = "Popover 弹窗内容",
Type = "string",
ValueList = "",
DefaultValue = "Popover"
},
new AttributeItem() {
Name = "IsHtml",
Description = "内容中是否包含 Html 代码",
Type = "boolean",
ValueList = "",
DefaultValue = "false"
},
new AttributeItem() {
Name = "Placement",
Description = "位置",
Type = "Placement",
ValueList = "Auto / Top / Left / Bottom / Right",
DefaultValue = "Auto"
},
new AttributeItem() {
Name = "Title",
Description = "Popover 弹窗标题",
Type = "string",
ValueList = "",
DefaultValue = "Popover"
},
// TODO: 移动到数据库中
new AttributeItem()
{
Name = "Cotent",
Description = "Popover 弹窗内容",
Type = "string",
ValueList = "",
DefaultValue = "Popover"
},
new AttributeItem()
{
Name = "IsHtml",
Description = "内容中是否包含 Html 代码",
Type = "boolean",
ValueList = "",
DefaultValue = "false"
},
new AttributeItem()
{
Name = "Placement",
Description = "位置",
Type = "Placement",
ValueList = "Auto / Top / Left / Bottom / Right",
DefaultValue = "Auto"
},
new AttributeItem()
{
Name = "Title",
Description = "Popover 弹窗标题",
Type = "string",
ValueList = "",
DefaultValue = "Popover"
}
};
}

View File

@ -74,7 +74,12 @@ public partial class BootstrapInputNumber<TValue>
[NotNull]
private IStringLocalizer<BootstrapInputNumber<TValue>>? Localizer { get; set; }
static BootstrapInputNumber()
/// <summary>
/// SetParametersAsync 方法
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
public override Task SetParametersAsync(ParameterView parameters)
{
// Unwrap Nullable<T>, because InputBase already deals with the Nullable aspect
// of it for us. We will only get asked to parse the T for nonempty inputs.
@ -83,6 +88,8 @@ public partial class BootstrapInputNumber<TValue>
{
throw new InvalidOperationException($"The type '{targetType}' is not a supported numeric type.");
}
return base.SetParametersAsync(parameters);
}
/// <summary>
@ -120,17 +127,24 @@ public partial class BootstrapInputNumber<TValue>
? Formatter.Invoke(value)
: (!string.IsNullOrEmpty(FormatString) && value != null
? Utility.Format(value, FormatString)
: value switch
{
null => null,
int @int => BindConverter.FormatValue(@int, CultureInfo.InvariantCulture),
long @long => BindConverter.FormatValue(@long, CultureInfo.InvariantCulture),
short @short => BindConverter.FormatValue(@short, CultureInfo.InvariantCulture),
float @float => BindConverter.FormatValue(@float, CultureInfo.InvariantCulture),
double @double => BindConverter.FormatValue(@double, CultureInfo.InvariantCulture),
decimal @decimal => BindConverter.FormatValue(@decimal, CultureInfo.InvariantCulture),
_ => throw new InvalidOperationException($"Unsupported type {value!.GetType()}"),
});
: InternalFormat(value));
/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
protected virtual string? InternalFormat(TValue value) => value switch
{
int @int => BindConverter.FormatValue(@int, CultureInfo.InvariantCulture),
long @long => BindConverter.FormatValue(@long, CultureInfo.InvariantCulture),
short @short => BindConverter.FormatValue(@short, CultureInfo.InvariantCulture),
float @float => BindConverter.FormatValue(@float, CultureInfo.InvariantCulture),
double @double => BindConverter.FormatValue(@double, CultureInfo.InvariantCulture),
decimal @decimal => BindConverter.FormatValue(@decimal, CultureInfo.InvariantCulture),
_ => throw new InvalidOperationException($"Unsupported type {value!.GetType()}"),
};
private void SetStep()
{

View File

@ -130,9 +130,9 @@ public partial class ValidateForm : IAsyncDisposable
if (validator != null)
{
var results = new List<ValidationResult>
{
new ValidationResult(errorMessage, new string[] { fieldName })
};
{
new ValidationResult(errorMessage, new string[] { fieldName })
};
validator.ToggleMessage(results, true);
}
}
@ -150,9 +150,9 @@ public partial class ValidateForm : IAsyncDisposable
if (validator != null)
{
var results = new List<ValidationResult>
{
new ValidationResult(errorMessage, new string[] { fieldName })
};
{
new ValidationResult(errorMessage, new string[] { fieldName })
};
validator.ToggleMessage(results, true);
}
}

View File

@ -0,0 +1,167 @@
// 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.DataAnnotations;
namespace UnitTest.Components;
public class InputNumberTest : BootstrapBlazorTestBase
{
[Fact]
public void OnBlur_Ok()
{
var cut = Context.RenderComponent<BootstrapInputNumber<int>>(pb =>
{
pb.Add(a => a.Min, "0");
pb.Add(a => a.Max, "10");
pb.Add(a => a.Step, "2");
});
cut.Contains("min=\"0\"");
cut.Contains("max=\"10\"");
cut.Contains("step=\"2\"");
var input = cut.Find("input");
cut.InvokeAsync(() => input.Blur());
}
[Fact]
public void ValidateForm()
{
var foo = new Cat() { Count = 20 };
var cut = Context.RenderComponent<ValidateForm>(pb =>
{
pb.Add(a => a.Model, foo);
pb.AddChildContent<BootstrapInputNumber<int>>(pb =>
{
pb.Add(a => a.Value, foo.Count);
pb.Add(a => a.ValueExpression, Utility.GenerateValueExpression(foo, nameof(Cat.Count), typeof(int)));
});
});
cut.Contains("class=\"form-label\"");
var input = cut.Find("input");
cut.InvokeAsync(() => input.Change(""));
var form = cut.Find("form");
cut.InvokeAsync(() => form.Submit());
cut.Contains("is-invalid");
}
[Fact]
public void InvalidOperationException_Error()
{
Assert.ThrowsAny<InvalidOperationException>(() => Context.RenderComponent<BootstrapInputNumber<string>>());
}
[Fact]
public void Formatter_Ok()
{
var cut = Context.RenderComponent<BootstrapInputNumber<decimal>>(pb =>
{
pb.Add(a => a.Value, 10.01m);
pb.Add(a => a.Formatter, v => $"{v + 1}");
});
var input = cut.Find("input");
Assert.Equal("11.01", input.GetAttribute("value"));
cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.Formatter, null);
pb.Add(a => a.FormatString, "#0.0");
});
Assert.Equal("10.0", input.GetAttribute("value"));
input = cut.Find("input");
cut.InvokeAsync(() => input.Change(""));
}
[Fact]
public void Formatter_Error()
{
Assert.ThrowsAny<InvalidOperationException>(() => Context.RenderComponent<MockInputNumber>());
}
[Fact]
public void ShowButton_Ok()
{
var inc = false;
var dec = false;
var cut = Context.RenderComponent<BootstrapInputNumber<int>>(pb =>
{
pb.Add(a => a.ShowButton, true);
pb.Add(a => a.OnIncrement, v =>
{
dec = true;
return Task.CompletedTask;
});
pb.Add(a => a.OnDecrement, v =>
{
inc = true;
return Task.CompletedTask;
});
});
cut.Contains("class=\"input-group\"");
var buttons = cut.FindAll("button");
cut.InvokeAsync(() => buttons[0].Click());
Assert.True(inc);
cut.InvokeAsync(() => buttons[1].Click());
Assert.True(dec);
}
[Theory]
[InlineData(typeof(short))]
[InlineData(typeof(int))]
[InlineData(typeof(long))]
[InlineData(typeof(float))]
[InlineData(typeof(double))]
[InlineData(typeof(decimal))]
public void Type_Ok(Type t)
{
var cut = Context.Render(builder =>
{
builder.OpenComponent(0, typeof(BootstrapInputNumber<>).MakeGenericType(t));
builder.AddAttribute(1, "ShowButton", true);
builder.AddAttribute(1, "Min", "-10");
builder.AddAttribute(1, "Max", "10");
builder.CloseComponent();
});
var buttons = cut.FindAll("button");
cut.InvokeAsync(() => buttons[0].Click());
cut.InvokeAsync(() => buttons[1].Click());
}
private class Cat
{
[Range(1, 10)]
public int Count { get; set; }
}
[ExcludeFromCodeCoverage]
private class MockInputNumber : BootstrapInputNumber<string>
{
public override Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
OnInitialized();
return Task.CompletedTask;
}
protected override string? InternalFormat(string value)
{
return base.InternalFormat(value);
}
protected override void OnInitialized()
{
base.OnInitialized();
InternalFormat("");
}
}
}