diff --git a/src/BootstrapBlazor.Shared/Samples/Popovers.razor.cs b/src/BootstrapBlazor.Shared/Samples/Popovers.razor.cs
index d9d2ba090..9d4066a9c 100644
--- a/src/BootstrapBlazor.Shared/Samples/Popovers.razor.cs
+++ b/src/BootstrapBlazor.Shared/Samples/Popovers.razor.cs
@@ -23,34 +23,38 @@ public sealed partial class Popovers
///
private static IEnumerable 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"
+ }
};
}
diff --git a/src/BootstrapBlazor/Components/InputNumber/BootstrapInputNumber.razor.cs b/src/BootstrapBlazor/Components/InputNumber/BootstrapInputNumber.razor.cs
index df1ab9892..107835972 100644
--- a/src/BootstrapBlazor/Components/InputNumber/BootstrapInputNumber.razor.cs
+++ b/src/BootstrapBlazor/Components/InputNumber/BootstrapInputNumber.razor.cs
@@ -74,7 +74,12 @@ public partial class BootstrapInputNumber
[NotNull]
private IStringLocalizer>? Localizer { get; set; }
- static BootstrapInputNumber()
+ ///
+ /// SetParametersAsync 方法
+ ///
+ ///
+ ///
+ public override Task SetParametersAsync(ParameterView parameters)
{
// Unwrap Nullable, 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
{
throw new InvalidOperationException($"The type '{targetType}' is not a supported numeric type.");
}
+
+ return base.SetParametersAsync(parameters);
}
///
@@ -120,17 +127,24 @@ public partial class BootstrapInputNumber
? 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));
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ 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()
{
diff --git a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs
index 1e60c0944..600d00432 100644
--- a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs
+++ b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs
@@ -130,9 +130,9 @@ public partial class ValidateForm : IAsyncDisposable
if (validator != null)
{
var results = new List
- {
- 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
- {
- new ValidationResult(errorMessage, new string[] { fieldName })
- };
+ {
+ new ValidationResult(errorMessage, new string[] { fieldName })
+ };
validator.ToggleMessage(results, true);
}
}
diff --git a/test/UnitTest/Components/InputNumberTest.cs b/test/UnitTest/Components/InputNumberTest.cs
new file mode 100644
index 000000000..9b33ff56d
--- /dev/null
+++ b/test/UnitTest/Components/InputNumberTest.cs
@@ -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>(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(pb =>
+ {
+ pb.Add(a => a.Model, foo);
+ pb.AddChildContent>(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(() => Context.RenderComponent>());
+ }
+
+ [Fact]
+ public void Formatter_Ok()
+ {
+ var cut = Context.RenderComponent>(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(() => Context.RenderComponent());
+ }
+
+ [Fact]
+ public void ShowButton_Ok()
+ {
+ var inc = false;
+ var dec = false;
+ var cut = Context.RenderComponent>(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
+ {
+ 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("");
+ }
+ }
+}