From a2df2e2387b0206177fbb72c904efae30b6c2db5 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 Jan 2026 20:01:11 +0800 Subject: [PATCH 1/6] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Components/Select/Select.razor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/Components/Select/Select.razor.cs b/src/BootstrapBlazor/Components/Select/Select.razor.cs index 2682b60667c..f279c6dcac1 100644 --- a/src/BootstrapBlazor/Components/Select/Select.razor.cs +++ b/src/BootstrapBlazor/Components/Select/Select.razor.cs @@ -281,7 +281,7 @@ private bool TryParseSelectItem(string value, [MaybeNullWhen(false)] out TValue // support SelectedItem? type result = SelectedItem != null ? (TValue)(object)SelectedItem : default; - validationErrorMessage = ""; + validationErrorMessage = null; return SelectedItem != null; } From 789a17ebdd9878710f42bcc485103633bbc4776e Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 Jan 2026 20:11:46 +0800 Subject: [PATCH 2/6] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Components/Validate/ValidateBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/BootstrapBlazor/Components/Validate/ValidateBase.cs b/src/BootstrapBlazor/Components/Validate/ValidateBase.cs index b07d93e77a4..a8a4b01237d 100644 --- a/src/BootstrapBlazor/Components/Validate/ValidateBase.cs +++ b/src/BootstrapBlazor/Components/Validate/ValidateBase.cs @@ -119,6 +119,11 @@ protected string CurrentValueAsString PreviousParsingAttemptFailed = false; CurrentValue = parsedValue; } + else if (string.IsNullOrEmpty(validationErrorMessage)) + { + // validationErrorMessage 为 null 表示转换目标值失败组件值未改变 + PreviousParsingAttemptFailed = false; + } else { PreviousParsingAttemptFailed = true; From d2352b40721a176572ea4c488a9e15671ae9756a Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 Jan 2026 20:11:57 +0800 Subject: [PATCH 3/6] chore: bump version 10.2.1-beta04 --- src/BootstrapBlazor/BootstrapBlazor.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index c510f140fd0..ba0651b92a8 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 10.2.1-beta03 + 10.2.1-beta04 From 08efa926f09afad2e0668af434fca312656f66ad Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 Jan 2026 20:38:28 +0800 Subject: [PATCH 4/6] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UnitTest/Components/DateTimePickerTest.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/UnitTest/Components/DateTimePickerTest.cs b/test/UnitTest/Components/DateTimePickerTest.cs index 01278e42c64..835ebd37e93 100644 --- a/test/UnitTest/Components/DateTimePickerTest.cs +++ b/test/UnitTest/Components/DateTimePickerTest.cs @@ -1255,6 +1255,41 @@ await cut.InvokeAsync(() => Assert.Equal("02/15/2024 01:00:00", cut.Instance.Value.ToString("MM/dd/yyyy HH:mm:ss")); } + + [Fact] + public async Task ValidateForm_IsEditable_Ok() + { + var model = new Foo() { DateTime = new DateTime(2024, 2, 15) }; + var cut = Context.Render(pb => + { + pb.Add(a => a.Model, model); + pb.AddChildContent>(builder => + { + builder.Add(a => a.IsEditable, true); + builder.Add(a => a.ViewMode, DatePickerViewMode.Date); + builder.Add(a => a.DateFormat, "MM/dd/yyyy"); + builder.Add(a => a.Value, model.DateTime); + builder.Add(a => a.ValueChanged, EventCallback.Factory.Create(this, v => + { + model.DateTime = v; + })); + builder.Add(a => a.ValueExpression, Utility.GenerateValueExpression(model, nameof(model.DateTime), typeof(DateTime?))); + }); + }); + var input = cut.Find(".datetime-picker-input"); + + // 更改成非法数值 测试 CurrentValueAsString 赋值逻辑 + await cut.InvokeAsync(() => + { + input.Change("00/15/2024"); + }); + Assert.NotNull(model.DateTime); + Assert.Equal("02/15/2024", model.DateTime.Value.ToString("MM/dd/yyyy")); + + var valid = cut.Instance.Validate(); + Assert.True(valid); + } + [Fact] public void MinValueToEmpty_Ok() { From 6c1ea524df416425f0f6772961d356ba70a8f717 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 Jan 2026 21:02:17 +0800 Subject: [PATCH 5/6] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Components/Validate/ValidateBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BootstrapBlazor/Components/Validate/ValidateBase.cs b/src/BootstrapBlazor/Components/Validate/ValidateBase.cs index a8a4b01237d..d7ff994b2af 100644 --- a/src/BootstrapBlazor/Components/Validate/ValidateBase.cs +++ b/src/BootstrapBlazor/Components/Validate/ValidateBase.cs @@ -136,7 +136,7 @@ protected string CurrentValueAsString if (FieldIdentifier != null) { - _parsingValidationMessages?.Add(FieldIdentifier.Value, PreviousErrorMessage ?? ""); + _parsingValidationMessages?.Add(FieldIdentifier.Value, PreviousErrorMessage); // Since we're not writing to CurrentValue, we'll need to notify about modification from here EditContext?.NotifyFieldChanged(FieldIdentifier.Value); From 5d3698643528ada0451d743a025845758619a016 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 1 Jan 2026 21:02:25 +0800 Subject: [PATCH 6/6] =?UTF-8?q?test:=20=E6=9B=B4=E6=96=B0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UnitTest/Components/DateTimePickerTest.cs | 2 +- test/UnitTest/Components/InputNumberTest.cs | 82 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/test/UnitTest/Components/DateTimePickerTest.cs b/test/UnitTest/Components/DateTimePickerTest.cs index 835ebd37e93..5095a367d37 100644 --- a/test/UnitTest/Components/DateTimePickerTest.cs +++ b/test/UnitTest/Components/DateTimePickerTest.cs @@ -1255,7 +1255,6 @@ await cut.InvokeAsync(() => Assert.Equal("02/15/2024 01:00:00", cut.Instance.Value.ToString("MM/dd/yyyy HH:mm:ss")); } - [Fact] public async Task ValidateForm_IsEditable_Ok() { @@ -1286,6 +1285,7 @@ await cut.InvokeAsync(() => Assert.NotNull(model.DateTime); Assert.Equal("02/15/2024", model.DateTime.Value.ToString("MM/dd/yyyy")); + // 录入非法数值 Validate 通过 var valid = cut.Instance.Validate(); Assert.True(valid); } diff --git a/test/UnitTest/Components/InputNumberTest.cs b/test/UnitTest/Components/InputNumberTest.cs index 14362306672..78686218ed3 100644 --- a/test/UnitTest/Components/InputNumberTest.cs +++ b/test/UnitTest/Components/InputNumberTest.cs @@ -216,6 +216,88 @@ public void Type_Ok(Type t) cut.InvokeAsync(() => buttons[1].Click()); } + [Fact] + public async Task Validate_Ok() + { + var model = new Foo() { Count = 1 }; + var cut = Context.Render(pb => + { + pb.Add(a => a.Model, model); + pb.AddChildContent>(builder => + { + builder.Add(a => a.Value, model.Count); + builder.Add(a => a.ValueChanged, EventCallback.Factory.Create(this, v => + { + model.Count = v; + })); + builder.Add(a => a.ValueExpression, Utility.GenerateValueExpression(model, nameof(model.Count), typeof(int))); + }); + }); + var input = cut.Find(".form-control"); + + // 更改成非法数值 测试 CurrentValueAsString 赋值逻辑 + await cut.InvokeAsync(() => + { + input.Change("t"); + }); + Assert.Equal(1, model.Count); + + var valid = cut.Instance.Validate(); + Assert.False(valid); + + await cut.InvokeAsync(() => + { + input.Change("t2"); + }); + Assert.Equal(1, model.Count); + valid = cut.Instance.Validate(); + Assert.False(valid); + + await cut.InvokeAsync(() => + { + input.Change("2"); + }); + Assert.Equal(2, model.Count); + + valid = cut.Instance.Validate(); + Assert.True(valid); + } + + [Fact] + public async Task TryParseValueFromString_Ok() + { + var model = new Foo() { Count = 1 }; + var cut = Context.Render>(pb => + { + pb.Add(a => a.Value, 1); + pb.Add(a => a.ValueChanged, EventCallback.Factory.Create(this, v => + { + model.Count = v; + })); + pb.Add(a => a.ValueExpression, Utility.GenerateValueExpression(model, nameof(model.Count), typeof(int))); + }); + var input = cut.Find(".form-control"); + + // 更改成非法数值 测试 CurrentValueAsString 赋值逻辑 + await cut.InvokeAsync(() => + { + input.Change("t"); + }); + Assert.Equal(1, model.Count); + + await cut.InvokeAsync(() => + { + input.Change("t2"); + }); + Assert.Equal(1, model.Count); + + await cut.InvokeAsync(() => + { + input.Change("2"); + }); + Assert.Equal(2, model.Count); + } + private class Cat { [Range(1, 10)]