diff --git a/src/Blazored.FluentValidation/EditContextFluentValidationExtensions.cs b/src/Blazored.FluentValidation/EditContextFluentValidationExtensions.cs index b217c43..a848ab2 100644 --- a/src/Blazored.FluentValidation/EditContextFluentValidationExtensions.cs +++ b/src/Blazored.FluentValidation/EditContextFluentValidationExtensions.cs @@ -129,6 +129,8 @@ private static ValidationContext ConstructValidationContext(EditContext context = new ValidationContext(editContext.Model); } + fluentValidationValidator.ConfigureContext?.Invoke(context); + return context; } diff --git a/src/Blazored.FluentValidation/FluentValidationsValidator.cs b/src/Blazored.FluentValidation/FluentValidationsValidator.cs index 4cd5273..729359f 100644 --- a/src/Blazored.FluentValidation/FluentValidationsValidator.cs +++ b/src/Blazored.FluentValidation/FluentValidationsValidator.cs @@ -17,6 +17,7 @@ public class FluentValidationValidator : ComponentBase [Parameter] public IValidator? Validator { get; set; } [Parameter] public bool DisableAssemblyScanning { get; set; } [Parameter] public Action>? Options { get; set; } + [Parameter] public Action>? ConfigureContext { get; set; } internal Action>? ValidateOptions { get; set; } internal Dictionary>? LastValidationResult { get; set; } diff --git a/tests/Blazored.FluentValidation.Tests/Model/Address.cs b/tests/Blazored.FluentValidation.Tests/Model/Address.cs index 0eeebc2..0fc39bf 100644 --- a/tests/Blazored.FluentValidation.Tests/Model/Address.cs +++ b/tests/Blazored.FluentValidation.Tests/Model/Address.cs @@ -11,6 +11,7 @@ public record Address public class AddressValidator : AbstractValidator
{ + 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"; @@ -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); } } } diff --git a/tests/Blazored.FluentValidation.Tests/ParamValidationContext/Component.razor b/tests/Blazored.FluentValidation.Tests/ParamValidationContext/Component.razor new file mode 100644 index 0000000..9f9d4fb --- /dev/null +++ b/tests/Blazored.FluentValidation.Tests/ParamValidationContext/Component.razor @@ -0,0 +1,48 @@ +@* Needs to be in different namespace than Blazored.FluentValidation.*. Otherwise ValidationContext cannot be resolved in .net8*@ +@namespace BlazoredFluentValidation.Tests.ParamValidationContext + + + + + +

+ + +

+ +

+ + +

+ +

+ + +

+ +

+ + +

+ +
+ +@code { + private readonly Address _address = new(); + internal readonly Dictionary 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 rootContextData) + { + foreach (var item in ContextData) + { + rootContextData[item.Key] = item.Value; + } + } +} diff --git a/tests/Blazored.FluentValidation.Tests/ParamValidationContext/Tests.cs b/tests/Blazored.FluentValidation.Tests/ParamValidationContext/Tests.cs new file mode 100644 index 0000000..292101a --- /dev/null +++ b/tests/Blazored.FluentValidation.Tests/ParamValidationContext/Tests.cs @@ -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(); + 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(); + 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(); + 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 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" + }; + } +}