Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ private static ValidationContext<object> ConstructValidationContext(EditContext
context = new ValidationContext<object>(editContext.Model);
}

fluentValidationValidator.ConfigureContext?.Invoke(context);

return context;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class FluentValidationValidator : ComponentBase
[Parameter] public IValidator? Validator { get; set; }
[Parameter] public bool DisableAssemblyScanning { get; set; }
[Parameter] public Action<ValidationStrategy<object>>? Options { get; set; }
[Parameter] public Action<ValidationContext<object>>? ConfigureContext { get; set; }
internal Action<ValidationStrategy<object>>? ValidateOptions { get; set; }
internal Dictionary<FieldIdentifier, List<ValidationFailure>>? LastValidationResult { get; set; }

Expand Down
7 changes: 6 additions & 1 deletion tests/Blazored.FluentValidation.Tests/Model/Address.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public record Address

public class AddressValidator : AbstractValidator<Address>
{
public const string IgnorePostcodeFlag = "IgnorePostcode";
public const string Line1Required = "You must enter Line 1";
public const string TownRequired = "You must enter a town";
public const string CountyRequired = "You must enter a county";
Expand All @@ -21,7 +22,11 @@ public AddressValidator()
RuleFor(p => p.Line1).NotEmpty().WithMessage(Line1Required);
RuleFor(p => p.Town).NotEmpty().WithMessage(TownRequired);
RuleFor(p => p.County).NotEmpty().WithMessage(CountyRequired);
RuleFor(p => p.Postcode).NotEmpty().WithMessage(PostcodeRequired);
RuleFor(p => p.Postcode)
.NotEmpty().WithMessage(PostcodeRequired)
.When((_, ctx) =>
!ctx.RootContextData.TryGetValue(IgnorePostcodeFlag, out var ignorePostcode)
|| ignorePostcode is false);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@* Needs to be in different namespace than Blazored.FluentValidation.*. Otherwise ValidationContext cannot be resolved in .net8*@
@namespace BlazoredFluentValidation.Tests.ParamValidationContext
<EditForm
Model="@_address"
OnValidSubmit="ValidSubmit"
OnInvalidSubmit="InvalidSubmit">

<FluentValidationValidator ConfigureContext="@(ctx => SetContextData(ctx.RootContextData))"/>
<ValidationSummary/>

<p>
<label>Address Line 1: </label>
<InputText name="@nameof(_address.Line1)" @bind-Value="@_address.Line1"/>
</p>

<p>
<label>Town: </label>
<InputText name="@nameof(_address.Town)" @bind-Value="@_address.Town"/>
</p>

<p>
<label>County: </label>
<InputText name="@nameof(_address.County)" @bind-Value="@_address.County"/>
</p>

<p>
<label>Postcode: </label>
<InputText name="@nameof(_address.Postcode)" @bind-Value="@_address.Postcode"/>
</p>
<button type="submit">Save</button>
</EditForm>

@code {
private readonly Address _address = new();
internal readonly Dictionary<string, object> ContextData = new(); // Internal to be used in tests to control component behavior
internal ValidationResultType? SubmitResult { get; set; } = ValidationResultType.Valid;

private void ValidSubmit() => SubmitResult = ValidationResultType.Valid;
private void InvalidSubmit() => SubmitResult = ValidationResultType.Error;

private void SetContextData(IDictionary<string, object> rootContextData)
{
foreach (var item in ContextData)
{
rootContextData[item.Key] = item.Value;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using Blazored.FluentValidation.Tests.Model;
using BlazoredFluentValidation.Tests.ParamValidationContext;
using static Blazored.FluentValidation.Tests.Model.AddressValidator;

namespace Blazored.FluentValidation.Tests.ParamValidationContext;

public class Tests : TestContext
{
private readonly Fixture _fixture = new();

[Fact]
public void Validate_PostcodeEmptyAndIgnorePostcodeContextNotSet_InvalidSubmit()
{
// Arrange
var cut = RenderComponent<Component>();
var address = _fixture.ValidAddress();
address.Postcode = "";
// Act
FillForm(cut, address);
cut.Find("button").Click();

// Assert
cut.Instance.SubmitResult.Should().Be(ValidationResultType.Error);
}


[Fact]
public void Validate_PostcodeEmptyAndIgnorePostcodeContextFalse_InvalidSubmit()
{
// Arrange
var cut = RenderComponent<Component>();
var address = _fixture.ValidAddress();
address.Postcode = "";
cut.Instance.ContextData.Add(IgnorePostcodeFlag, false);

// Act
FillForm(cut, address);
cut.Find("button").Click();

// Assert
cut.Instance.SubmitResult.Should().Be(ValidationResultType.Error);
}

[Fact]
public void Validate_PostcodeEmptyAndIgnorePostcodeContextTrue_ValidSubmit()
{
// Arrange
var cut = RenderComponent<Component>();
var address = _fixture.ValidAddress();
address.Postcode = "";
cut.Instance.ContextData.Add(IgnorePostcodeFlag, true);

// Act
FillForm(cut, address);
cut.Find("button").Click();
cut.WaitForState(() => cut.Instance.SubmitResult == ValidationResultType.Valid);
// Assert
cut.Instance.SubmitResult.Should().Be(ValidationResultType.Valid);
}

private static void FillForm(IRenderedComponent<Component> cut, Address address)
{
cut.Find($"input[name={nameof(Address.Line1)}]").Change(address.Line1);
cut.Find($"input[name={nameof(Address.Town)}]").Change(address.Town);
cut.Find($"input[name={nameof(Address.County)}]").Change(address.County);
cut.Find($"input[name={nameof(Address.Postcode)}]").Change(address.Postcode);
}

private class Fixture
{
public Address ValidAddress() => new()
{
Line1 = "123 Main St",
Town = "Springfield",
Postcode = "12345",
County = "Real"
};
}
}