Skip to content

fix(ValidateBase): do not add validate state when ErrorMessage is null#7463

Merged
ArgoZhang merged 6 commits intomainfrom
fix-validate
Jan 2, 2026
Merged

fix(ValidateBase): do not add validate state when ErrorMessage is null#7463
ArgoZhang merged 6 commits intomainfrom
fix-validate

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Jan 2, 2026

Link issues

fixes #7455

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Adjust validation behavior so failed parsing without an error message does not mark fields invalid, and ensure form validation reflects unchanged values correctly.

Bug Fixes:

  • Prevent validation state from being added when parsing fails but no validation error message is provided, avoiding incorrect invalid states for unchanged values.
  • Fix select component parsing to return a null validation error message when no item is selected instead of an empty string, aligning with updated validation logic.

Tests:

  • Add input number validation tests to cover repeated invalid input, value changes, and overall form validation results.
  • Add datetime picker validation test to verify editable input with invalid text does not incorrectly fail form validation.

Copilot AI review requested due to automatic review settings January 2, 2026 07:26
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Jan 2, 2026

Reviewer's Guide

Adjusts ValidateBase parsing logic to avoid adding validation state when no error message is produced, aligns Select parsing to use null error messages, and adds unit tests for InputNumber and DateTimePicker to verify validation behavior with invalid input that does not change the model value.

Sequence diagram for ValidateBase parsing and validation state update

sequenceDiagram
    actor User
    participant InputComponent as InputComponent
    participant ValidateBase as ValidateBase
    participant FieldParser as FieldParser
    participant EditContext as EditContext

    User->>InputComponent: change input value
    InputComponent->>ValidateBase: set CurrentValueAsString(newValue)
    ValidateBase->>FieldParser: TryParseValueFromString(newValue, out parsedValue, out validationErrorMessage)

    alt parsing succeeds (validationErrorMessage is not null or empty)
        FieldParser-->>ValidateBase: parsedValue, validationErrorMessage
        ValidateBase->>ValidateBase: PreviousParsingAttemptFailed = true
        ValidateBase->>ValidateBase: PreviousErrorMessage = validationErrorMessage
        ValidateBase->>ValidateBase: add message to _parsingValidationMessages
        ValidateBase->>EditContext: NotifyFieldChanged(FieldIdentifier)
    else parsing fails but validationErrorMessage is null or empty
        FieldParser-->>ValidateBase: parsedValue (unchanged), validationErrorMessage = null
        ValidateBase->>ValidateBase: PreviousParsingAttemptFailed = false
        ValidateBase-->>EditContext: no validation state added
    end
Loading

Updated class diagram for ValidateBase and Select parsing behavior

classDiagram
    class ValidateBase~TValue~ {
        - bool PreviousParsingAttemptFailed
        - string PreviousErrorMessage
        - IDictionary FieldIdentifierToMessages _parsingValidationMessages
        - FieldIdentifier~TValue~? FieldIdentifier
        - EditContext EditContext
        + string CurrentValueAsString
        + void SetCurrentValueAsString(string value)
    }

    class Select~TValue~ {
        + object SelectedItem
        + bool TryParseSelectItem(string value, out TValue result, out string validationErrorMessage)
    }

    class EditContext {
        + void NotifyFieldChanged(FieldIdentifier fieldIdentifier)
    }

    class FieldIdentifier~TValue~ {
        + object Model
        + string FieldName
    }

    ValidateBase~TValue~ o-- EditContext : uses
    ValidateBase~TValue~ o-- FieldIdentifier~TValue~ : identifies
    ValidateBase~TValue~ ..> Select~TValue~ : consumes parsing result
    Select~TValue~ ..> ValidateBase~TValue~ : supplies validationErrorMessage

    note for Select~TValue~ "When SelectedItem is not null and parsing succeeds:
- result is cast from SelectedItem
- validationErrorMessage is null (no validation state added by ValidateBase)"
Loading

File-Level Changes

Change Details Files
Refine validation parsing behavior to distinguish between failed parses that change validation state and cases with no error message.
  • Update CurrentValueAsString setter in ValidateBase to treat null or empty validationErrorMessage as a non-failing parse that leaves PreviousParsingAttemptFailed false when the component value does not change.
  • Ensure that only non-null PreviousErrorMessage entries are added to _parsingValidationMessages, preventing empty-string messages from being recorded.
  • Maintain EditContext field change notifications only when appropriate after parsing.
src/BootstrapBlazor/Components/Validate/ValidateBase.cs
Align Select component parsing to signal no validation error when SelectedItem resolution succeeds without an error message.
  • Change TryParseSelectItem to set validationErrorMessage to null instead of an empty string when a value is successfully parsed from SelectedItem.
  • Return result based on SelectedItem while relying on the new null-based error signaling in validation logic.
src/BootstrapBlazor/Components/Select/Select.razor.cs
Add regression tests to cover validation and parsing behavior for InputNumber and DateTimePicker under invalid input scenarios.
  • Add InputNumber Validate_Ok test to verify that repeated invalid string inputs do not change the model, that Validate() returns false until a valid value is entered, and that the model updates only on valid input.
  • Add InputNumber TryParseValueFromString_Ok test to assert that invalid input sequences preserve the model value and valid input updates it.
  • Add DateTimePicker ValidateForm_IsEditable_Ok test to ensure that with IsEditable enabled, invalid date text input does not alter the underlying model value and Validate() still passes when the stored value remains valid.
test/UnitTest/Components/InputNumberTest.cs
test/UnitTest/Components/DateTimePickerTest.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#7455 Ensure that when a DateTimePicker inside a ValidateForm receives an invalid/illegal text input, the form validation behaves correctly and does not remain permanently failing due to that invalid input (particularly when the underlying bound value has not changed).
#7455 Adjust the generic validation infrastructure (ValidateBase and related components such as Select/InputNumber) so that failed parsing with a null validationErrorMessage does not add a validation error state, thereby avoiding spurious validation failures in form components.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@bb-auto bb-auto Bot added the bug Something isn't working label Jan 2, 2026
@bb-auto bb-auto Bot added this to the v10.2.0 milestone Jan 2, 2026
@ArgoZhang ArgoZhang merged commit 03ebf66 into main Jan 2, 2026
4 of 5 checks passed
@ArgoZhang ArgoZhang deleted the fix-validate branch January 2, 2026 07:27
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In ValidateBase.CurrentValueAsString setter, the new branch uses string.IsNullOrEmpty(validationErrorMessage) but the comment and behavior distinguish only the null case (not empty); consider tightening this to validationErrorMessage == null or updating the comment/semantics so empty-string errors are handled consistently and intentionally.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In ValidateBase.CurrentValueAsString setter, the new branch uses string.IsNullOrEmpty(validationErrorMessage) but the comment and behavior distinguish only the null case (not empty); consider tightening this to `validationErrorMessage == null` or updating the comment/semantics so empty-string errors are handled consistently and intentionally.

## Individual Comments

### Comment 1
<location> `test/UnitTest/Components/InputNumberTest.cs:219-228` </location>
<code_context>
+    [Fact]
</code_context>

<issue_to_address>
**suggestion (testing):** Strengthen validation assertions to verify field-level validation state, not only the overall form validity flag.

In `Validate_Ok`, consider also asserting the field-level validation messages on the `EditContext` (e.g., via `EditContext.GetValidationMessages(FieldIdentifier)` or a helper) after each change. That will verify that invalid input actually adds an error and valid input clears it, catching regressions in validation state even when the overall `Validate()` bool still returns the expected value.

Suggested implementation:

```csharp
    [Fact]
    public async Task Validate_Ok()
    {
        var model = new Foo() { Count = 1 };
        FieldIdentifier countField = new(model, nameof(Foo.Count));
        IReadOnlyList<string> GetCountFieldMessages(EditContext editContext)
            => editContext.GetValidationMessages(countField).ToList();

        var cut = Context.Render<ValidateForm>(pb =>
        {
            pb.Add(a => a.Model, model);
            pb.AddChildContent<BootstrapInputNumber<int>>(builder =>
            {
                builder.Add(a => a.Value, model.Count);
                builder.Add(a => a.ValueChanged, EventCallback.Factory.Create<int>(this, v =>

```

To fully implement the suggestion, update the rest of `Validate_Ok` to:
1. Obtain the `EditContext` from the rendered `ValidateForm` instance (e.g., `var editContext = cut.Instance.EditContext;` or via the contained `EditForm` depending on how `ValidateForm` is implemented).
2. After each interaction that changes `Foo.Count` and triggers validation (for both invalid and valid values), add assertions like:
   - `Assert.NotEmpty(GetCountFieldMessages(editContext));` and assert expected message text for invalid input.
   - `Assert.Empty(GetCountFieldMessages(editContext));` once the input is corrected and validation passes.
If `FieldIdentifier`, `EditContext`, or `ToList()` are not already in scope, ensure the file has `using Microsoft.AspNetCore.Components.Forms;` and `using System.Linq;` at the top.
</issue_to_address>

### Comment 2
<location> `test/UnitTest/Components/DateTimePickerTest.cs:1258-1267` </location>
<code_context>
+    [Fact]
</code_context>

<issue_to_address>
**suggestion (testing):** Extend the DateTimePicker validation test to assert the absence of validation messages for the field after invalid editable input.

In `ValidateForm_IsEditable_Ok`, you already confirm the model value is unchanged and `Validate()` returns true after an invalid editable value. To better cover the change that skips adding validation state when the error message is null, please also assert that `EditContext.GetValidationMessages` for the DateTime field is empty. This verifies the field itself is not marked invalid despite the parse failure and strengthens future validation regression coverage.

Suggested implementation:

```csharp
    [Fact]
    public async Task ValidateForm_IsEditable_Ok()
    {
        var model = new Foo() { DateTime = new DateTime(2024, 2, 15) };
        var cut = Context.Render<ValidateForm>(pb =>
        {
            pb.Add(a => a.Model, model);
            pb.AddChildContent<DateTimePicker<DateTime?>>(builder =>
            {
                builder.Add(a => a.IsEditable, true);
                builder.Add(a => a.ViewMode, DatePickerViewMode.Date);

```

To implement the suggestion, inside `ValidateForm_IsEditable_Ok` after you simulate an invalid editable input, assert the model value is unchanged, and call `Validate()` (or `cut.Instance.EditContext.Validate()`) and assert it returns `true`, add an assertion that the `EditContext` has no validation messages for the `DateTime` field.

Concretely, right after your existing assertion that validation succeeds, add something along these lines:

```csharp
// existing code (example)
var editContext = cut.Instance.EditContext;
var isValid = editContext.Validate();
Assert.True(isValid);
Assert.Equal(new DateTime(2024, 2, 15), model.DateTime); // existing assertion

// new assertion: no validation messages for the DateTime field
var messages = editContext.GetValidationMessages(
    FieldIdentifier.Create(() => model.DateTime)
);
Assert.Empty(messages);
```

If you are using the lambda-based overload of `GetValidationMessages`, you can also write:

```csharp
Assert.Empty(editContext.GetValidationMessages(() => model.DateTime));
```

Make sure `editContext` is the same `EditContext` instance you already use for `Validate()`, and keep this new assertion after the invalid editable input has been processed to ensure the field itself is not marked invalid despite the parse failure.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +1258 to +1267
[Fact]
public async Task ValidateForm_IsEditable_Ok()
{
var model = new Foo() { DateTime = new DateTime(2024, 2, 15) };
var cut = Context.Render<ValidateForm>(pb =>
{
pb.Add(a => a.Model, model);
pb.AddChildContent<DateTimePicker<DateTime?>>(builder =>
{
builder.Add(a => a.IsEditable, true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Extend the DateTimePicker validation test to assert the absence of validation messages for the field after invalid editable input.

In ValidateForm_IsEditable_Ok, you already confirm the model value is unchanged and Validate() returns true after an invalid editable value. To better cover the change that skips adding validation state when the error message is null, please also assert that EditContext.GetValidationMessages for the DateTime field is empty. This verifies the field itself is not marked invalid despite the parse failure and strengthens future validation regression coverage.

Suggested implementation:

    [Fact]
    public async Task ValidateForm_IsEditable_Ok()
    {
        var model = new Foo() { DateTime = new DateTime(2024, 2, 15) };
        var cut = Context.Render<ValidateForm>(pb =>
        {
            pb.Add(a => a.Model, model);
            pb.AddChildContent<DateTimePicker<DateTime?>>(builder =>
            {
                builder.Add(a => a.IsEditable, true);
                builder.Add(a => a.ViewMode, DatePickerViewMode.Date);

To implement the suggestion, inside ValidateForm_IsEditable_Ok after you simulate an invalid editable input, assert the model value is unchanged, and call Validate() (or cut.Instance.EditContext.Validate()) and assert it returns true, add an assertion that the EditContext has no validation messages for the DateTime field.

Concretely, right after your existing assertion that validation succeeds, add something along these lines:

// existing code (example)
var editContext = cut.Instance.EditContext;
var isValid = editContext.Validate();
Assert.True(isValid);
Assert.Equal(new DateTime(2024, 2, 15), model.DateTime); // existing assertion

// new assertion: no validation messages for the DateTime field
var messages = editContext.GetValidationMessages(
    FieldIdentifier.Create(() => model.DateTime)
);
Assert.Empty(messages);

If you are using the lambda-based overload of GetValidationMessages, you can also write:

Assert.Empty(editContext.GetValidationMessages(() => model.DateTime));

Make sure editContext is the same EditContext instance you already use for Validate(), and keep this new assertion after the invalid editable input has been processed to ensure the field itself is not marked invalid despite the parse failure.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 2, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (c50ef3f) to head (5d36986).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #7463   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          749       749           
  Lines        32908     32912    +4     
  Branches      4572      4573    +1     
=========================================
+ Hits         32908     32912    +4     
Flag Coverage Δ
BB 100.00% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes issue #7455 by preventing validation error states from being added when components fail to parse input but intentionally return a null error message. This allows components like DateTimePicker and Select to silently reject invalid input while maintaining the previous valid value, rather than triggering form validation errors.

Key Changes:

  • Added conditional logic in ValidateBase to treat null/empty validation error messages as non-failures
  • Updated Select component to return null instead of empty string for validation error messages when item lookup fails
  • Bumped version to 10.2.1-beta04

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/BootstrapBlazor/Components/Validate/ValidateBase.cs Added logic to handle null validation error messages by setting PreviousParsingAttemptFailed to false, preventing validation state changes when components intentionally suppress errors
src/BootstrapBlazor/Components/Select/Select.razor.cs Changed validationErrorMessage from empty string to null to signal parsing failure without validation error
test/UnitTest/Components/InputNumberTest.cs Added tests to verify validation behavior with invalid input and ensure parsing failures are properly handled
test/UnitTest/Components/DateTimePickerTest.cs Added test to verify DateTimePicker with IsEditable correctly handles invalid input without triggering validation errors
src/BootstrapBlazor/BootstrapBlazor.csproj Version bump to 10.2.1-beta04

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(ValidateForm): DateTimePicker 组件录入非法数值导致验证无法通过

2 participants