From 534a8998c29a058b196b32fd81b89d08ded06656 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 15 Jan 2019 21:48:14 +0100 Subject: [PATCH] Validation improved. --- .../Validators/CollectionValidator.cs | 30 +++- .../Validators/PatternValidator.cs | 2 +- .../Validators/RangeValidator.cs | 26 ++- .../Validators/StringLengthValidator.cs | 26 ++- .../Apps/Guards/GuardApp.cs | 4 +- .../Apps/Guards/GuardAppClients.cs | 14 +- .../Apps/Guards/GuardAppContributors.cs | 8 +- .../Apps/Guards/GuardAppLanguages.cs | 6 +- .../Apps/Guards/GuardAppPatterns.cs | 14 +- .../Apps/Guards/GuardAppRoles.cs | 8 +- .../Assets/Guards/GuardAsset.cs | 4 +- .../Comments/Guards/GuardComments.cs | 4 +- .../Contents/Guards/GuardContent.cs | 4 +- .../Rules/Guards/GuardRule.cs | 6 +- .../Rules/Guards/RuleTriggerValidator.cs | 4 +- .../Guards/FieldPropertiesValidator.cs | 60 +++---- .../Schemas/Guards/GuardSchema.cs | 6 +- .../Schemas/Guards/GuardSchemaField.cs | 6 +- src/Squidex.Infrastructure/Guard.cs | 4 +- src/Squidex.Infrastructure/Not.cs | 100 +++++++++++ .../angular/forms/control-errors.component.ts | 33 +--- .../angular/forms/error-formatting.spec.ts | 160 ++++++++++++++++++ .../angular/forms/error-formatting.ts | 70 ++++++++ .../angular/forms/validators.spec.ts | 122 ++++++++++--- .../app/framework/angular/forms/validators.ts | 111 +++++++----- .../app/shared/state/contents.forms.spec.ts | 12 +- .../app/shared/state/contents.forms.ts | 72 ++------ .../ValidateContent/ArrayFieldTests.cs | 20 ++- .../ValidateContent/AssetsFieldTests.cs | 12 +- .../ValidateContent/ContentValidationTests.cs | 4 +- .../ValidateContent/DateTimeFieldTests.cs | 4 +- .../ValidateContent/NumberFieldTests.cs | 4 +- .../ValidateContent/ReferencesFieldTests.cs | 12 +- .../ValidateContent/StringFieldTests.cs | 6 +- .../ValidateContent/TagsFieldTests.cs | 12 +- .../CollectionItemValidatorTests.cs | 4 +- .../Validators/CollectionValidatorTests.cs | 36 +++- .../Validators/PatternValidatorTests.cs | 2 +- .../Validators/RangeValidatorTests.cs | 16 +- .../Validators/StringLengthValidatorTests.cs | 27 ++- .../Apps/Guards/GuardAppClientsTests.cs | 2 +- .../Apps/Guards/GuardAppContributorsTests.cs | 2 +- .../Apps/Guards/GuardAppLanguagesTests.cs | 2 +- .../Apps/Guards/GuardAppPatternsTests.cs | 4 +- .../Apps/Guards/GuardAppTests.cs | 2 +- .../Contents/Guard/GuardContentTests.cs | 2 +- .../ArrayFieldPropertiesTests.cs | 12 +- .../AssetsFieldPropertiesTests.cs | 40 ++++- .../DateTimeFieldPropertiesTests.cs | 6 +- .../NumberFieldPropertiesTests.cs | 4 +- .../ReferencesFieldPropertiesTests.cs | 14 +- .../StringFieldPropertiesTests.cs | 14 +- .../TagsFieldPropertiesTests.cs | 14 +- .../Schemas/Guards/GuardSchemaFieldTests.cs | 6 +- .../Schemas/Guards/GuardSchemaTests.cs | 6 +- 55 files changed, 897 insertions(+), 308 deletions(-) create mode 100644 src/Squidex.Infrastructure/Not.cs create mode 100644 src/Squidex/app/framework/angular/forms/error-formatting.spec.ts create mode 100644 src/Squidex/app/framework/angular/forms/error-formatting.ts diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs index 820afe308..a858b811c 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Collections; using System.Threading.Tasks; using Squidex.Infrastructure.Tasks; @@ -19,6 +20,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators public CollectionValidator(bool isRequired, int? minItems = null, int? maxItems = null) { + if (minItems.HasValue && maxItems.HasValue && minItems.Value > maxItems.Value) + { + throw new ArgumentException("Min length must be greater than max length.", nameof(minItems)); + } + this.isRequired = isRequired; this.minItems = minItems; this.maxItems = maxItems; @@ -36,14 +42,28 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators return TaskHelper.Done; } - if (minItems.HasValue && items.Count < minItems.Value) + if (minItems.HasValue && maxItems.HasValue) { - addError(context.Path, $"Must have at least {minItems} item(s)."); + if (minItems == maxItems && minItems != items.Count) + { + addError(context.Path, $"Must have exactly {maxItems} item(s)."); + } + else if (items.Count < minItems || items.Count > maxItems) + { + addError(context.Path, $"Must have between {minItems} and {maxItems} item(s)."); + } } - - if (maxItems.HasValue && items.Count > maxItems.Value) + else { - addError(context.Path, $"Must have not more than {maxItems} item(s)."); + if (minItems.HasValue && items.Count < minItems.Value) + { + addError(context.Path, $"Must have at least {minItems} item(s)."); + } + + if (maxItems.HasValue && items.Count > maxItems.Value) + { + addError(context.Path, $"Must not have more than {maxItems} item(s)."); + } } return TaskHelper.Done; diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs index c358c3bdf..af952f7fb 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs @@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { if (string.IsNullOrWhiteSpace(errorMessage)) { - addError(context.Path, "Not valid."); + addError(context.Path, "Does not match to the pattern."); } else { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs index a2b617477..52a72ba4c 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs @@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators public RangeValidator(T? min, T? max) { - if (min.HasValue && max.HasValue && min.Value.CompareTo(max.Value) >= 0) + if (min.HasValue && max.HasValue && min.Value.CompareTo(max.Value) > 0) { throw new ArgumentException("Min value must be greater than max value.", nameof(min)); } @@ -36,14 +36,28 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators var typedValue = (T)value; - if (min.HasValue && typedValue.CompareTo(min.Value) < 0) + if (min.HasValue && max.HasValue) { - addError(context.Path, $"Must be greater than or equal to '{min}'."); + if (Equals(min, max) && Equals(min.Value, max.Value)) + { + addError(context.Path, $"Must be exactly '{max}'."); + } + else if (typedValue.CompareTo(min.Value) < 0 || typedValue.CompareTo(max.Value) > 0) + { + addError(context.Path, $"Must be between '{min}' and '{max}'."); + } } - - if (max.HasValue && typedValue.CompareTo(max.Value) > 0) + else { - addError(context.Path, $"Must be less than or equal to '{max}'."); + if (min.HasValue && typedValue.CompareTo(min.Value) < 0) + { + addError(context.Path, $"Must be greater or equal to '{min}'."); + } + + if (max.HasValue && typedValue.CompareTo(max.Value) > 0) + { + addError(context.Path, $"Must be less or equal to '{max}'."); + } } return TaskHelper.Done; diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs index f89b1ed3e..b6579a7bc 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs @@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators public StringLengthValidator(int? minLength, int? maxLength) { - if (minLength.HasValue && maxLength.HasValue && minLength.Value >= maxLength.Value) + if (minLength.HasValue && maxLength.HasValue && minLength.Value > maxLength.Value) { throw new ArgumentException("Min length must be greater than max length.", nameof(minLength)); } @@ -31,14 +31,28 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { if (value is string stringValue && !string.IsNullOrEmpty(stringValue)) { - if (minLength.HasValue && stringValue.Length < minLength.Value) + if (minLength.HasValue && maxLength.HasValue) { - addError(context.Path, $"Must have more than '{minLength}' characters."); + if (minLength == maxLength && minLength != stringValue.Length) + { + addError(context.Path, $"Must have exactly {maxLength} character(s)."); + } + else if (stringValue.Length < minLength || stringValue.Length > maxLength) + { + addError(context.Path, $"Must have between {minLength} and {maxLength} character(s)."); + } } - - if (maxLength.HasValue && stringValue.Length > maxLength.Value) + else { - addError(context.Path, $"Must have less than '{maxLength}' characters."); + if (minLength.HasValue && stringValue.Length < minLength.Value) + { + addError(context.Path, $"Must have at least {minLength} character(s)."); + } + + if (maxLength.HasValue && stringValue.Length > maxLength.Value) + { + addError(context.Path, $"Must not have more than {maxLength} character(s)."); + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs index 49787bba8..ea2cad6a9 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardApp.cs @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (!command.Name.IsSlug()) { - e("Name must be a valid slug.", nameof(command.Name)); + e(Not.ValidSlug("Name"), nameof(command.Name)); } }); } @@ -36,7 +36,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (string.IsNullOrWhiteSpace(command.PlanId)) { - e("Plan id is required.", nameof(command.PlanId)); + e(Not.Defined("Plan id"), nameof(command.PlanId)); return; } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs index d33ad053c..31dfcf653 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppClients.cs @@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (string.IsNullOrWhiteSpace(command.Id)) { - e("Client id is required.", nameof(command.Id)); + e(Not.Defined("Client id"), nameof(command.Id)); } else if (clients.ContainsKey(command.Id)) { @@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (string.IsNullOrWhiteSpace(command.Id)) { - e("Client id is required.", nameof(command.Id)); + e(Not.Defined("Client id"), nameof(command.Id)); } }); } @@ -55,17 +55,17 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (string.IsNullOrWhiteSpace(command.Id)) { - e("Client id is required.", nameof(command.Id)); + e(Not.Defined("Client id"), nameof(command.Id)); } if (string.IsNullOrWhiteSpace(command.Name) && command.Role == null) { - e("Either name or role must be defined.", nameof(command.Name), nameof(command.Role)); + e(Not.DefinedOr("name", "role"), nameof(command.Name), nameof(command.Role)); } if (command.Role != null && !roles.ContainsKey(command.Role)) { - e("Role is not valid.", nameof(command.Role)); + e(Not.Valid("role"), nameof(command.Role)); } if (client == null) @@ -75,12 +75,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (!string.IsNullOrWhiteSpace(command.Name) && string.Equals(client.Name, command.Name)) { - e("Client has already this name.", nameof(command.Name)); + e(Not.New("Client", "name"), nameof(command.Name)); } if (command.Role == client.Role) { - e("Client has already this role.", nameof(command.Role)); + e(Not.New("Client", "role"), nameof(command.Role)); } }); } diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs index befae1246..17d1702d7 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppContributors.cs @@ -26,12 +26,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (!roles.ContainsKey(command.Role)) { - e("Role is not valid.", nameof(command.Role)); + e(Not.Valid("role"), nameof(command.Role)); } if (string.IsNullOrWhiteSpace(command.ContributorId)) { - e("Contributor id is required.", nameof(command.ContributorId)); + e(Not.Defined("Contributor id"), nameof(command.ContributorId)); return; } @@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (existing == command.Role) { - e("Contributor has already this role.", nameof(command.Role)); + e(Not.New("Contributor", "role"), nameof(command.Role)); } } else if (plan.MaxContributors == contributors.Count) @@ -71,7 +71,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (string.IsNullOrWhiteSpace(command.ContributorId)) { - e("Contributor id is required.", nameof(command.ContributorId)); + e(Not.Defined("Contributor id"), nameof(command.ContributorId)); } var ownerIds = contributors.Where(x => x.Value == Role.Owner).Select(x => x.Key).ToList(); diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs index 183249ec3..c9fe5aaf6 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppLanguages.cs @@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (command.Language == null) { - e("Language code is required.", nameof(command.Language)); + e(Not.Defined("Language code"), nameof(command.Language)); } else if (languages.Contains(command.Language)) { @@ -40,7 +40,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (command.Language == null) { - e("Language code is required.", nameof(command.Language)); + e(Not.Defined("Language code"), nameof(command.Language)); } if (languages.Master == config) @@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (command.Language == null) { - e("Language is required.", nameof(command.Language)); + e(Not.Defined("Language code"), nameof(command.Language)); } if ((languages.Master == config || command.IsMaster) && command.IsOptional) diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs index 44780591f..685441e53 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppPatterns.cs @@ -22,12 +22,12 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (command.PatternId == Guid.Empty) { - e("Id is required.", nameof(command.PatternId)); + e(Not.Defined("Id"), nameof(command.PatternId)); } if (string.IsNullOrWhiteSpace(command.Name)) { - e("Name is required.", nameof(command.Name)); + e(Not.Defined("Name"), nameof(command.Name)); } if (patterns.Values.Any(x => x.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) @@ -37,11 +37,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (string.IsNullOrWhiteSpace(command.Pattern)) { - e("Pattern is required.", nameof(command.Pattern)); + e(Not.Defined("Pattern"), nameof(command.Pattern)); } else if (!command.Pattern.IsValidRegex()) { - e("Pattern is not a valid regular expression.", nameof(command.Pattern)); + e(Not.Valid("Pattern"), nameof(command.Pattern)); } if (patterns.Values.Any(x => x.Pattern == command.Pattern)) @@ -74,7 +74,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (string.IsNullOrWhiteSpace(command.Name)) { - e("Name is required.", nameof(command.Name)); + e(Not.Defined("Name"), nameof(command.Name)); } if (patterns.Any(x => x.Key != command.PatternId && x.Value.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase))) @@ -84,11 +84,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (string.IsNullOrWhiteSpace(command.Pattern)) { - e("Pattern is required.", nameof(command.Pattern)); + e(Not.Defined("Pattern"), nameof(command.Pattern)); } else if (!command.Pattern.IsValidRegex()) { - e("Pattern is not a valid regular expression.", nameof(command.Pattern)); + e(Not.Valid("Pattern"), nameof(command.Pattern)); } if (patterns.Any(x => x.Key != command.PatternId && x.Value.Pattern == command.Pattern)) diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs index 179ea162b..da129c04e 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Guards/GuardAppRoles.cs @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (string.IsNullOrWhiteSpace(command.Name)) { - e("Name is required.", nameof(command.Name)); + e(Not.Defined("Name"), nameof(command.Name)); } else if (roles.ContainsKey(command.Name)) { @@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (string.IsNullOrWhiteSpace(command.Name)) { - e("Name is required.", nameof(command.Name)); + e(Not.Defined("Name"), nameof(command.Name)); } else if (Role.IsDefaultRole(command.Name)) { @@ -71,7 +71,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards { if (string.IsNullOrWhiteSpace(command.Name)) { - e("Name is required.", nameof(command.Name)); + e(Not.Defined("Name"), nameof(command.Name)); } else if (Role.IsDefaultRole(command.Name)) { @@ -80,7 +80,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards if (command.Permissions == null) { - e("Permissions is required.", nameof(command.Permissions)); + e(Not.Defined("Permissions"), nameof(command.Permissions)); } }); } diff --git a/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs b/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs index f9970aa1f..68d727f4c 100644 --- a/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs +++ b/src/Squidex.Domain.Apps.Entities/Assets/Guards/GuardAsset.cs @@ -20,12 +20,12 @@ namespace Squidex.Domain.Apps.Entities.Assets.Guards { if (string.IsNullOrWhiteSpace(command.FileName)) { - e("Name is required.", nameof(command.FileName)); + e(Not.Defined("Name"), nameof(command.FileName)); } if (string.Equals(command.FileName, oldName)) { - e("Asset has already this name.", nameof(command.FileName)); + e(Not.New("Asset", "name"), nameof(command.FileName)); } }); } diff --git a/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs b/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs index 90ba851ce..aae11c511 100644 --- a/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs +++ b/src/Squidex.Domain.Apps.Entities/Comments/Guards/GuardComments.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards { if (string.IsNullOrWhiteSpace(command.Text)) { - e("Text is required.", nameof(command.Text)); + e(Not.Defined("Text"), nameof(command.Text)); } }); } @@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Comments.Guards { if (string.IsNullOrWhiteSpace(command.Text)) { - e("Text is required.", nameof(command.Text)); + e(Not.Defined("Text"), nameof(command.Text)); } }); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs index cdb0b309a..194432381 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs @@ -73,7 +73,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards { if (!StatusFlow.Exists(command.Status)) { - e("Status is not valid.", nameof(command.Status)); + e(Not.Valid("Status"), nameof(command.Status)); } else if (!StatusFlow.CanChange(status, command.Status)) { @@ -111,7 +111,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards { if (command.Data == null) { - e("Data is required.", nameof(command.Data)); + e(Not.Defined("Data"), nameof(command.Data)); } } } diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs b/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs index b0dc20d30..7a991c0c3 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Guards/GuardRule.cs @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { if (command.Trigger == null) { - e("Trigger is required.", nameof(command.Trigger)); + e(Not.Defined("Trigger"), nameof(command.Trigger)); } else { @@ -34,7 +34,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards if (command.Action == null) { - e("Action is required.", nameof(command.Action)); + e(Not.Defined("Action"), nameof(command.Action)); } else { @@ -53,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { if (command.Trigger == null && command.Action == null) { - e("Either trigger or action is required.", nameof(command.Trigger), nameof(command.Action)); + e(Not.Defined("Either trigger or action"), nameof(command.Trigger), nameof(command.Action)); } if (command.Trigger != null) diff --git a/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs index e855d1c21..3c95fc2d1 100644 --- a/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Rules/Guards/RuleTriggerValidator.cs @@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards if (trigger.NumDays.HasValue && (trigger.NumDays < 1 || trigger.NumDays > 30)) { - errors.Add(new ValidationError("Num days must be between 1 and 30.", nameof(trigger.NumDays))); + errors.Add(new ValidationError(Not.Between("Num days", 1, 30), nameof(trigger.NumDays))); } return Task.FromResult< IEnumerable>(errors); @@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.Guards { if (schema.SchemaId == Guid.Empty) { - errors.Add(new ValidationError("Schema id is required.", nameof(trigger.Schemas))); + errors.Add(new ValidationError(Not.Defined("Schema id"), nameof(trigger.Schemas))); } else { diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs index 19eea457e..5b3453edf 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/FieldPropertiesValidator.cs @@ -26,9 +26,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards public IEnumerable Visit(ArrayFieldProperties properties) { - if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value >= properties.MaxItems.Value) + if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value) { - yield return new ValidationError("Max items must be greater than min items.", + yield return new ValidationError(Not.GreaterEquals("Max items", "min items"), nameof(properties.MinItems), nameof(properties.MaxItems)); } @@ -36,37 +36,37 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards public IEnumerable Visit(AssetsFieldProperties properties) { - if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value >= properties.MaxItems.Value) + if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value) { - yield return new ValidationError("Max items must be greater than min items.", + yield return new ValidationError(Not.GreaterEquals("Max items", "min items"), nameof(properties.MinItems), nameof(properties.MaxItems)); } - if (properties.MaxHeight.HasValue && properties.MinHeight.HasValue && properties.MinHeight.Value >= properties.MaxHeight.Value) + if (properties.MaxHeight.HasValue && properties.MinHeight.HasValue && properties.MinHeight.Value > properties.MaxHeight.Value) { - yield return new ValidationError("Max height must be greater than min height.", + yield return new ValidationError(Not.GreaterEquals("Max height", "min height"), nameof(properties.MaxHeight), nameof(properties.MinHeight)); } - if (properties.MaxWidth.HasValue && properties.MinWidth.HasValue && properties.MinWidth.Value >= properties.MaxWidth.Value) + if (properties.MaxWidth.HasValue && properties.MinWidth.HasValue && properties.MinWidth.Value > properties.MaxWidth.Value) { - yield return new ValidationError("Max width must be greater than min width.", + yield return new ValidationError(Not.GreaterEquals("Max width", "min width"), nameof(properties.MaxWidth), nameof(properties.MinWidth)); } if (properties.MaxSize.HasValue && properties.MinSize.HasValue && properties.MinSize.Value >= properties.MaxSize.Value) { - yield return new ValidationError("Max size must be greater than min size.", + yield return new ValidationError(Not.GreaterThan("Max size", "min size"), nameof(properties.MaxSize), nameof(properties.MinSize)); } if (properties.AspectWidth.HasValue != properties.AspectHeight.HasValue) { - yield return new ValidationError("Aspect width and height is required.", + yield return new ValidationError(Not.Defined2("Aspect width", "aspect height"), nameof(properties.AspectWidth), nameof(properties.AspectHeight)); } @@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError("Editor is not a valid value.", + yield return new ValidationError(Not.Valid("Editor"), nameof(properties.Editor)); } } @@ -85,25 +85,25 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError("Editor is not a valid value.", + yield return new ValidationError(Not.Valid("Editor"), nameof(properties.Editor)); } if (properties.DefaultValue.HasValue && properties.MinValue.HasValue && properties.DefaultValue.Value < properties.MinValue.Value) { - yield return new ValidationError("Default value must be greater than min value.", + yield return new ValidationError(Not.GreaterEquals("Default value", "min value"), nameof(properties.DefaultValue)); } if (properties.DefaultValue.HasValue && properties.MaxValue.HasValue && properties.DefaultValue.Value > properties.MaxValue.Value) { - yield return new ValidationError("Default value must be less than max value.", + yield return new ValidationError(Not.LessEquals("Default value", "max value"), nameof(properties.DefaultValue)); } if (properties.MaxValue.HasValue && properties.MinValue.HasValue && properties.MinValue.Value >= properties.MaxValue.Value) { - yield return new ValidationError("Max value must be greater than min value.", + yield return new ValidationError(Not.GreaterThan("Max value", "min value"), nameof(properties.MinValue), nameof(properties.MaxValue)); } @@ -112,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.CalculatedDefaultValue.Value.IsEnumValue()) { - yield return new ValidationError("Calculated default value is not valid.", + yield return new ValidationError(Not.Valid("Calculated default value"), nameof(properties.CalculatedDefaultValue)); } @@ -129,7 +129,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError("Editor is not a valid value.", + yield return new ValidationError(Not.Valid("Editor"), nameof(properties.Editor)); } } @@ -143,7 +143,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError("Editor is not a valid value.", + yield return new ValidationError(Not.Valid("Editor"), nameof(properties.Editor)); } @@ -155,19 +155,19 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (properties.DefaultValue.HasValue && properties.MinValue.HasValue && properties.DefaultValue.Value < properties.MinValue.Value) { - yield return new ValidationError("Default value must be greater than min value.", + yield return new ValidationError(Not.GreaterEquals("Default value", "min value"), nameof(properties.DefaultValue)); } if (properties.DefaultValue.HasValue && properties.MaxValue.HasValue && properties.DefaultValue.Value > properties.MaxValue.Value) { - yield return new ValidationError("Default value must be less than max value.", + yield return new ValidationError(Not.LessEquals("Default value", "max value"), nameof(properties.DefaultValue)); } if (properties.MaxValue.HasValue && properties.MinValue.HasValue && properties.MinValue.Value >= properties.MaxValue.Value) { - yield return new ValidationError("Max value must be greater than min value.", + yield return new ValidationError(Not.GreaterThan("Max value", "min value"), nameof(properties.MinValue), nameof(properties.MaxValue)); } @@ -190,9 +190,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards public IEnumerable Visit(ReferencesFieldProperties properties) { - if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value >= properties.MaxItems.Value) + if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value) { - yield return new ValidationError("Max items must be greater than min items.", + yield return new ValidationError(Not.GreaterEquals("Max items", "min items"), nameof(properties.MinItems), nameof(properties.MaxItems)); } @@ -202,7 +202,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError("Editor is not a valid value.", + yield return new ValidationError(Not.Valid("Editor"), nameof(properties.Editor)); } @@ -214,13 +214,13 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (properties.Pattern != null && !properties.Pattern.IsValidRegex()) { - yield return new ValidationError("Pattern is not a valid expression.", + yield return new ValidationError(Not.Valid("Pattern"), nameof(properties.Pattern)); } - if (properties.MaxLength.HasValue && properties.MinLength.HasValue && properties.MinLength.Value >= properties.MaxLength.Value) + if (properties.MaxLength.HasValue && properties.MinLength.HasValue && properties.MinLength.Value > properties.MaxLength.Value) { - yield return new ValidationError("Max length must be greater than min length.", + yield return new ValidationError(Not.GreaterEquals("Max length", "min length"), nameof(properties.MinLength), nameof(properties.MaxLength)); } @@ -245,7 +245,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!properties.Editor.IsEnumValue()) { - yield return new ValidationError("Editor is not a valid value.", + yield return new ValidationError(Not.Valid("Editor"), nameof(properties.Editor)); } @@ -255,9 +255,9 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards nameof(properties.AllowedValues)); } - if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value >= properties.MaxItems.Value) + if (properties.MaxItems.HasValue && properties.MinItems.HasValue && properties.MinItems.Value > properties.MaxItems.Value) { - yield return new ValidationError("Max items must be greater than min items.", + yield return new ValidationError(Not.GreaterEquals("Max items", "min items"), nameof(properties.MinItems), nameof(properties.MaxItems)); } diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs index 31237a2ed..2df48f6e0 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchema.cs @@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (!command.Name.IsSlug()) { - e("Name is not a valid slug.", nameof(command.Name)); + e(Not.ValidSlug("Name"), nameof(command.Name)); } else if (await appProvider.GetSchemaAsync(command.AppId.Id, command.Name) != null) { @@ -44,7 +44,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (!field.Partitioning.IsValidPartitioning()) { - e("Field partitioning is not valid.", $"{fieldPrefix}.{nameof(field.Partitioning)}"); + e(Not.Valid("Partitioning"), $"{fieldPrefix}.{nameof(field.Partitioning)}"); } ValidateField(e, fieldPrefix, field); @@ -180,7 +180,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (field.Properties == null) { - e("Field properties is required.", $"{prefix}.{nameof(field.Properties)}"); + e(Not.Defined("Field properties"), $"{prefix}.{nameof(field.Properties)}"); } else { diff --git a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs index 7b7f6a313..83853330d 100644 --- a/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs +++ b/src/Squidex.Domain.Apps.Entities/Schemas/Guards/GuardSchemaField.cs @@ -27,7 +27,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards if (command.Properties == null) { - e("Properties is required.", nameof(command.Properties)); + e(Not.Defined("Properties"), nameof(command.Properties)); } else { @@ -49,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (command.ParentFieldId == null && !command.Partitioning.IsValidPartitioning()) { - e("Partitioning is not valid.", nameof(command.Partitioning)); + e(Not.Valid("Partitioning"), nameof(command.Partitioning)); } if (schema.FieldsByName.ContainsKey(command.Name)) @@ -75,7 +75,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards { if (command.Properties == null) { - e("Properties is required.", nameof(command.Properties)); + e(Not.Defined("Properties"), nameof(command.Properties)); } else { diff --git a/src/Squidex.Infrastructure/Guard.cs b/src/Squidex.Infrastructure/Guard.cs index f4a5c8d53..c9465c7fc 100644 --- a/src/Squidex.Infrastructure/Guard.cs +++ b/src/Squidex.Infrastructure/Guard.cs @@ -116,7 +116,7 @@ namespace Squidex.Infrastructure { if (target.CompareTo(lower) < 0) { - throw new ArgumentException($"Value must be greater than or equal to {lower}", parameterName); + throw new ArgumentException($"Value must be greater or equal to {lower}", parameterName); } } @@ -136,7 +136,7 @@ namespace Squidex.Infrastructure { if (target.CompareTo(upper) > 0) { - throw new ArgumentException($"Value must be less than or equal to {upper}", parameterName); + throw new ArgumentException($"Value must be less or equal to {upper}", parameterName); } } diff --git a/src/Squidex.Infrastructure/Not.cs b/src/Squidex.Infrastructure/Not.cs new file mode 100644 index 000000000..ca273ff34 --- /dev/null +++ b/src/Squidex.Infrastructure/Not.cs @@ -0,0 +1,100 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Runtime.CompilerServices; + +namespace Squidex.Infrastructure +{ + public static class Not + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Defined(string property) + { + return $"{Upper(property)} is required."; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Defined2(string property1, string property2) + { + return $"If {Lower(property1)} or {Lower(property2)} is used both must be defined."; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ValidSlug(string property) + { + return $"{Upper(property)} is not a valid slug."; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GreaterThan(string property, string other) + { + return $"{Upper(property)} must be greater than {Lower(other)}."; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GreaterEquals(string property, string other) + { + return $"{Upper(property)} must be greater or equal to {Lower(other)}."; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string LessThan(string property, string other) + { + return $"{Upper(property)} must be less than {Lower(other)}."; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string LessEquals(string property, string other) + { + return $"{Upper(property)} must be less or equal to {Lower(other)}."; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Between(string property, T min, T max) + { + return $"{Upper(property)} must be between {min} and {max}."; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Valid(string property) + { + return $"{Upper(property)} is not a valid value."; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string New(string type, string property) + { + return $"{Upper(type)} has already this {Lower(property)}."; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string DefinedOr(string property1, string property2) + { + return $"Either {Lower(property1)} or {Lower(property2)} must be defined."; + } + + private static string Lower(string property) + { + if (char.IsUpper(property[0])) + { + return char.ToLower(property[0]) + property.Substring(1); + } + + return property; + } + + private static string Upper(string property) + { + if (char.IsLower(property[0])) + { + return char.ToUpper(property[0]) + property.Substring(1); + } + + return property; + } + } +} diff --git a/src/Squidex/app/framework/angular/forms/control-errors.component.ts b/src/Squidex/app/framework/angular/forms/control-errors.component.ts index 86a8fed4e..673975dfc 100644 --- a/src/Squidex/app/framework/angular/forms/control-errors.component.ts +++ b/src/Squidex/app/framework/angular/forms/control-errors.component.ts @@ -11,20 +11,7 @@ import { merge, Subscription } from 'rxjs'; import { fadeAnimation, Types } from '@app/framework/internal'; -const DEFAULT_ERRORS: { [key: string]: string } = { - required: '{field} is required.', - pattern: '{field} does not follow the pattern.', - patternmessage: '{message}', - minvalue: '{field} must be larger or equals to {minValue}.', - maxvalue: '{field} must be smaller or equals to {maxValue}.', - minmax: '{field} must have a length of more than {requiredLength}.', - maxlength: '{field} must have a length of less than {requiredLength}.', - match: '{message}', - validdatetime: '{field} is not a valid date time', - validnumber: '{field} is not a valid number.', - validvalues: '{field} is not a valid value.', - validarrayvalues: '{field} contains an invalid value.' -}; +import { formatError } from './error-formatting'; @Component({ selector: 'sqx-control-errors', @@ -130,23 +117,11 @@ export class ControlErrorsComponent implements OnChanges, OnDestroy { if (this.control && this.control.invalid && ((this.control.touched && !this.submitOnly) || this.submitted) && this.control.errors) { for (let key in this.control.errors) { if (this.control.errors.hasOwnProperty(key)) { - let message = (this.errors ? this.errors[key] : null) || DEFAULT_ERRORS[key.toLowerCase()]; + const message = formatError(this.displayFieldName, key, this.control.errors[key], this.control.value, this.errors); - if (!message) { - continue; + if (message) { + errors.push(message); } - - const properties = this.control.errors[key]; - - for (let property in properties) { - if (properties.hasOwnProperty(property)) { - message = message.replace(`{${property}}`, properties[property]); - } - } - - message = message.replace('{field}', this.displayFieldName); - - errors.push(message); } } } diff --git a/src/Squidex/app/framework/angular/forms/error-formatting.spec.ts b/src/Squidex/app/framework/angular/forms/error-formatting.spec.ts new file mode 100644 index 000000000..746436056 --- /dev/null +++ b/src/Squidex/app/framework/angular/forms/error-formatting.spec.ts @@ -0,0 +1,160 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; + +import { ValidatorsEx } from './validators'; + +import { formatError } from './error-formatting'; + +describe('formatErrors', () => { + it('should format min', () => { + const error = validate(1, Validators.min(2)); + + expect(error).toEqual('MY_FIELD must be greater or equal to \'2\'.'); + }); + + it('should format max', () => { + const error = validate(3, Validators.max(2)); + + expect(error).toEqual('MY_FIELD must be less or equal to \'2\'.'); + }); + + it('should format required', () => { + const error = validate(undefined, Validators.required); + + expect(error).toEqual('MY_FIELD is required.'); + }); + + it('should format requiredTrue', () => { + const error = validate(undefined, Validators.requiredTrue); + + expect(error).toEqual('MY_FIELD is required.'); + }); + + it('should format email', () => { + const error = validate('invalid', Validators.email); + + expect(error).toEqual('MY_FIELD must be an email address.'); + }); + + it('should format minLength string', () => { + const error = validate('x', Validators.minLength(2)); + + expect(error).toEqual('MY_FIELD must have at least 2 character(s).'); + }); + + it('should format maxLength string', () => { + const error = validate('xxx', Validators.maxLength(2)); + + expect(error).toEqual('MY_FIELD must not have more than 2 character(s).'); + }); + + it('should format minLength array', () => { + const error = validate([1], Validators.minLength(2)); + + expect(error).toEqual('MY_FIELD must have at least 2 item(s).'); + }); + + it('should format maxLength array', () => { + const error = validate([1, 1, 1], Validators.maxLength(2)); + + expect(error).toEqual('MY_FIELD must not have more than 2 item(s).'); + }); + + it('should format match', () => { + const error = validate('123', Validators.pattern('[A-Z]')); + + expect(error).toEqual('MY_FIELD does not match to the pattern.'); + }); + + it('should format match with message', () => { + const error = validate('123', ValidatorsEx.pattern('[A-Z]', 'Custom Message')); + + expect(error).toEqual('Custom Message'); + }); + + it('should format between exactly', () => { + const error = validate(2, ValidatorsEx.between(3, 3)); + + expect(error).toEqual('MY_FIELD must be exactly \'3\'.'); + }); + + it('should format between range', () => { + const error = validate(2, ValidatorsEx.between(3, 5)); + + expect(error).toEqual('MY_FIELD must be between \'3\' and \'5\'.'); + }); + + it('should format betweenLength string exactly', () => { + const error = validate('xx', ValidatorsEx.betweenLength(3, 3)); + + expect(error).toEqual('MY_FIELD must have exactly 3 character(s).'); + }); + + it('should format betweenLength string range', () => { + const error = validate('xx', ValidatorsEx.betweenLength(3, 5)); + + expect(error).toEqual('MY_FIELD must have between 3 and 5 character(s).'); + }); + + it('should format betweenLength array exactly', () => { + const error = validate([1], ValidatorsEx.betweenLength(3, 3)); + + expect(error).toEqual('MY_FIELD must have exactly 3 item(s).'); + }); + + it('should format betweenLength array range', () => { + const error = validate([1, 1], ValidatorsEx.betweenLength(3, 5)); + + expect(error).toEqual('MY_FIELD must have between 3 and 5 item(s).'); + }); + + it('should format validDateTime', () => { + const error = validate('invalid', ValidatorsEx.validDateTime()); + + expect(error).toEqual('MY_FIELD is not a valid date time.'); + }); + + it('should format validValues', () => { + const error = validate(5, ValidatorsEx.validValues([1, 2, 3])); + + expect(error).toEqual('MY_FIELD is not a valid value.'); + }); + + it('should format validArrayValues', () => { + const error = validate([2, 4], ValidatorsEx.validArrayValues([1, 2, 3])); + + expect(error).toEqual('MY_FIELD contains an invalid value.'); + }); + + it('should format match', () => { + const formControl1 = new FormControl(1); + const formControl2 = new FormControl(2); + + const formGroup = new FormGroup({ + field1: formControl1, + field2: formControl2 + }); + + const formError = ValidatorsEx.match('field2', 'Passwords must match.')!(formControl1)!; + const formMessage = formatError('MY_FIELD', Object.keys(formError)[0], Object.values(formError)[0], undefined); + + expect(formMessage).toEqual('Passwords must match.'); + + formGroup.reset(); + }); + + function validate(value: any, validator: ValidatorFn) { + const formControl = new FormControl(value); + + const formError = validator(formControl)!; + const formMessage = formatError('MY_FIELD', Object.keys(formError)[0], Object.values(formError)[0], value); + + return formMessage; + } +}); \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/error-formatting.ts b/src/Squidex/app/framework/angular/forms/error-formatting.ts new file mode 100644 index 000000000..8efc7dc2a --- /dev/null +++ b/src/Squidex/app/framework/angular/forms/error-formatting.ts @@ -0,0 +1,70 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights r vbeserved + */ + +import { Types } from './../../utils/types'; + +const DEFAULT_ERRORS: { [key: string]: string } = { + between: '{field} must be between \'{min}\' and \'{max}\'.', + betweenlength: '{field} must have between {minlength} and {maxlength} item(s).', + betweenlengthstring: '{field} must have between {minlength} and {maxlength} character(s).', + email: '{field} must be an email address.', + exactly: '{field} must be exactly \'{expected}\'.', + exactlylength: '{field} must have exactly {expected} item(s).', + exactlylengthstring: '{field} must have exactly {expected} character(s).', + match: '{message}', + max: '{field} must be less or equal to \'{max}\'.', + maxlength: '{field} must not have more than {requiredlength} item(s).', + maxlengthstring: '{field} must not have more than {requiredlength} character(s).', + min: '{field} must be greater or equal to \'{min}\'.', + minlength: '{field} must have at least {requiredlength} item(s).', + minlengthstring: '{field} must have at least {requiredlength} character(s).', + pattern: '{field} does not match to the pattern.', + patternmessage: '{message}', + required: '{field} is required.', + requiredTrue: '{field} is required.', + validdatetime: '{field} is not a valid date time.', + validvalues: '{field} is not a valid value.', + validarrayvalues: '{field} contains an invalid value.' +}; + +export function formatError(field: string, type: string, properties: any, value: any, errors?: any) { + type = type.toLowerCase(); + + if (Types.isString(value)) { + if (type === 'minlength') { + type = 'minlengthstring'; + } + + if (type === 'maxlength') { + type = 'maxlengthstring'; + } + + if (type === 'exactlylength') { + type = 'exactlylengthstring'; + } + + if (type === 'betweenlength') { + type = 'betweenlengthstring'; + } + } + + let message = (errors ? errors[type] : null) || DEFAULT_ERRORS[type]; + + if (!message) { + return null; + } + + for (let property in properties) { + if (properties.hasOwnProperty(property)) { + message = message.replace(`{${property.toLowerCase()}}`, properties[property]); + } + } + + message = message.replace('{field}', field); + + return message; +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/validators.spec.ts b/src/Squidex/app/framework/angular/forms/validators.spec.ts index 51d538b51..1e0911720 100644 --- a/src/Squidex/app/framework/angular/forms/validators.spec.ts +++ b/src/Squidex/app/framework/angular/forms/validators.spec.ts @@ -18,6 +18,14 @@ describe('ValidatorsEx.between', () => { expect(validator).toBe(Validators.nullValidator); }); + it('should return null when value is equal to min and max', () => { + const input = new FormControl(3); + + const error = ValidatorsEx.between(3, 3)(input); + + expect(error).toBeNull(); + }); + it('should return null when value is valid', () => { const input = new FormControl(4); @@ -42,28 +50,108 @@ describe('ValidatorsEx.between', () => { expect(error).toBeNull(); }); - it('should return error when not a number', () => { - const input = new FormControl('text'); + it('should return error if less than min', () => { + const input = new FormControl(0); - const error = ValidatorsEx.between(1, 5)(input); + const error = ValidatorsEx.between(1, undefined)(input); - expect(error.validnumber).toBeFalsy(); + expect(error.min).toBeDefined(); }); - it('should return error if less than minimum setting', () => { - const input = new FormControl(-5); + it('should return error if greater than max', () => { + const input = new FormControl(6); - const error = ValidatorsEx.between(1, 5)(input); + const error = ValidatorsEx.between(undefined, 5)(input); - expect(error.minvalue).toBeDefined(); + expect(error.max).toBeDefined(); }); - it('should return error if greater than maximum setting', () => { - const input = new FormControl(300); + it('should return error if not in range', () => { + const input = new FormControl(1); - const error = ValidatorsEx.between(1, 5)(input); + const error = ValidatorsEx.between(2, 4)(input); + + expect(error.between).toBeDefined(); + }); + + it('should return error if not equal to min and max', () => { + const input = new FormControl(2); + + const error = ValidatorsEx.between(3, 3)(input); + + expect(error.exactly).toBeDefined(); + }); +}); + +describe('ValidatorsEx.betweenLength', () => { + it('should return null validator if no min value or max value', () => { + const validator = ValidatorsEx.betweenLength(undefined, undefined); + + expect(validator).toBe(Validators.nullValidator); + }); + + it('should return null when value is equal to min and max', () => { + const input = new FormControl('xxx'); + + const error = ValidatorsEx.betweenLength(3, 3)(input); + + expect(error).toBeNull(); + }); + + it('should return null when value is valid', () => { + const input = new FormControl('xxxx'); + + const error = ValidatorsEx.betweenLength(1, 5)(input); + + expect(error).toBeNull(); + }); + + it('should return null when value is null', () => { + const input = new FormControl(null); + + const error = ValidatorsEx.betweenLength(1, 5)(input); - expect(error.maxvalue).toBeDefined(); + expect(error).toBeNull(); + }); + + it('should return null when value is undefined', () => { + const input = new FormControl(undefined); + + const error = ValidatorsEx.betweenLength(1, 5)(input); + + expect(error).toBeNull(); + }); + + it('should return error if less than min', () => { + const input = new FormControl('x'); + + const error = ValidatorsEx.betweenLength(2, undefined)(input); + + expect(error.minlength).toBeDefined(); + }); + + it('should return error if greater than max', () => { + const input = new FormControl('xxxxxx'); + + const error = ValidatorsEx.betweenLength(undefined, 5)(input); + + expect(error.maxlength).toBeDefined(); + }); + + it('should return error if not in range', () => { + const input = new FormControl('x'); + + const error = ValidatorsEx.betweenLength(2, 4)(input); + + expect(error.betweenlength).toBeDefined(); + }); + + it('should return error if not equal to min and max', () => { + const input = new FormControl('xx'); + + const error = ValidatorsEx.betweenLength(3, 3)(input); + + expect(error.exactlylength).toBeDefined(); }); }); @@ -283,14 +371,4 @@ describe('ValidatorsEx.pattern', () => { expect(error).toEqual(expected); }); -}); - -describe('ValidatorsEx.noop', () => { - it('should return null validator', () => { - const input = new FormControl(null); - - const error = ValidatorsEx.noop()(input); - - expect(error).toBeNull(); - }); }); \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/validators.ts b/src/Squidex/app/framework/angular/forms/validators.ts index 84ca3959b..c972541fa 100644 --- a/src/Squidex/app/framework/angular/forms/validators.ts +++ b/src/Squidex/app/framework/angular/forms/validators.ts @@ -7,7 +7,11 @@ import { AbstractControl, ValidatorFn, Validators } from '@angular/forms'; -import { DateTime, Types } from '@app/framework/internal'; +import { DateTime } from '@app/framework/internal'; + +function isEmptyInputValue(value: any): boolean { + return value == null || value.length === 0; +} export module ValidatorsEx { export function pattern(regex: string | RegExp, message?: string): ValidatorFn { @@ -15,33 +19,16 @@ export module ValidatorsEx { return Validators.nullValidator; } - let regeExp: RegExp; - let regexStr: string; - - if (Types.isString(regex)) { - regexStr = `^${regex}$`; - regeExp = new RegExp(regexStr); - } else { - regexStr = regex.toString(); - regeExp = regex; - } + const inner = Validators.pattern(regex); return (control: AbstractControl) => { - const n: string = control.value; + const error = inner(control); - if (n == null || n.length === 0) { - return null; + if (error !== null && error.pattern && message) { + return { patternmessage: { requiredPattern: error.pattern.requiredPattern, actualValue: error.pattern.actualValue, message } }; } - if (!regeExp.test(n)) { - if (message) { - return { patternmessage: { requiredPattern: regexStr, actualValue: n, message } }; - } else { - return { pattern: { requiredPattern: regexStr, actualValue: n } }; - } - } - - return null; + return error; }; } @@ -89,26 +76,68 @@ export module ValidatorsEx { }; } - export function between(minValue?: number, maxValue?: number): ValidatorFn { - if (!minValue || !maxValue) { + export function between(min?: number, max?: number): ValidatorFn { + if (!min && !max) { return Validators.nullValidator; } - return (control: AbstractControl) => { - const value: number = control.value; - - if (!Types.isUndefined(value) && !Types.isNull(value)) { - if (!Types.isNumber(value)) { - return { validnumber: false }; - } else if (minValue && value < minValue) { - return { minvalue: { minValue, actualValue: value } }; - } else if (maxValue && value > maxValue) { - return { maxvalue: { maxValue, actualValue: value } }; + if (max && min) { + return (control: AbstractControl) => { + if (isEmptyInputValue(control.value)) { + return null; } - } - return null; - }; + const value = parseFloat(control.value); + + if (min === max) { + if (isNaN(value) || value !== min) { + return { exactly: { expected: min, actual: value } }; + } + } else { + if (isNaN(value) || value < min || value > max) { + return { between: { min: min, max: max, actual: value }}; + } + } + + return null; + }; + } else if (max) { + return Validators.max(max); + } else { + return Validators.min(min!); + } + } + + export function betweenLength(minLength?: number, maxLength?: number): ValidatorFn { + if (!minLength && !maxLength) { + return Validators.nullValidator; + } + + if (maxLength && minLength) { + return (control: AbstractControl) => { + if (isEmptyInputValue(control.value)) { + return null; + } + + const length: number = control.value ? control.value.length : 0; + + if (minLength === maxLength) { + if (isNaN(length) || length !== minLength) { + return { exactlylength: { expected: minLength, actual: length } }; + } + } else { + if (isNaN(length) || length < minLength || length > maxLength) { + return { betweenlength: { minLength, maxLength, actual: length }}; + } + } + + return null; + }; + } else if (maxLength) { + return Validators.maxLength(maxLength); + } else { + return Validators.minLength(minLength!); + } } export function validValues(values: T[]): ValidatorFn { @@ -146,10 +175,4 @@ export module ValidatorsEx { return null; }; } - - export function noop(): ValidatorFn { - return () => { - return null; - }; - } } \ No newline at end of file diff --git a/src/Squidex/app/shared/state/contents.forms.spec.ts b/src/Squidex/app/shared/state/contents.forms.spec.ts index 818541480..bdec52f8c 100644 --- a/src/Squidex/app/shared/state/contents.forms.spec.ts +++ b/src/Squidex/app/shared/state/contents.forms.spec.ts @@ -121,7 +121,7 @@ describe('ArrayField', () => { const field = createField(new ArrayFieldPropertiesDto({ isRequired: true, minItems: 1, maxItems: 5 })); it('should create validators', () => { - expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(3); + expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(2); }); it('should format to empty string if null', () => { @@ -145,7 +145,7 @@ describe('AssetsField', () => { const field = createField(new AssetsFieldPropertiesDto({ isRequired: true, minItems: 1, maxItems: 5 })); it('should create validators', () => { - expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(3); + expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(2); }); it('should format to empty string if null', () => { @@ -169,7 +169,7 @@ describe('TagsField', () => { const field = createField(new TagsFieldPropertiesDto('Tags', { isRequired: true, minItems: 1, maxItems: 5 })); it('should create validators', () => { - expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(3); + expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(2); }); it('should format to empty string if null', () => { @@ -306,7 +306,7 @@ describe('NumberField', () => { const field = createField(new NumberFieldPropertiesDto('Input', { isRequired: true, minValue: 1, maxValue: 6, allowedValues: [1, 3] })); it('should create validators', () => { - expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(4); + expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(3); }); it('should format to empty string if null', () => { @@ -328,7 +328,7 @@ describe('ReferencesField', () => { const field = createField(new ReferencesFieldPropertiesDto({ isRequired: true, minItems: 1, maxItems: 5 })); it('should create validators', () => { - expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(3); + expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(2); }); it('should format to empty string if null', () => { @@ -352,7 +352,7 @@ describe('StringField', () => { const field = createField(new StringFieldPropertiesDto('Input', { isRequired: true, pattern: 'pattern', minLength: 1, maxLength: 5, allowedValues: ['a', 'b'] })); it('should create validators', () => { - expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(5); + expect(FieldValidatorsFactory.createValidators(field, false).length).toBe(4); }); it('should format to empty string if null', () => { diff --git a/src/Squidex/app/shared/state/contents.forms.ts b/src/Squidex/app/shared/state/contents.forms.ts index 932a85fcc..02f96ce36 100644 --- a/src/Squidex/app/shared/state/contents.forms.ts +++ b/src/Squidex/app/shared/state/contents.forms.ts @@ -146,15 +146,9 @@ export class FieldValidatorsFactory implements FieldPropertiesVisitor 0) { const values: (number | null)[] = properties.allowedValues; @@ -170,15 +164,9 @@ export class FieldValidatorsFactory implements FieldPropertiesVisitor 0) { validators.push(ValidatorsEx.pattern(properties.pattern, properties.patternMessage)); @@ -198,57 +186,33 @@ export class FieldValidatorsFactory implements FieldPropertiesVisitor 0) { const values: (string | null)[] = properties.allowedValues; diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs index c29434bb8..d2df5f606 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ArrayFieldTests.cs @@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_not_add_error_if_tags_are_valid() + public async Task Should_not_add_error_if_items_are_valid() { var sut = Field(new ArrayFieldProperties()); @@ -38,7 +38,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_not_add_error_if_tags_are_null_and_valid() + public async Task Should_not_add_error_if_items_are_null_and_valid() { var sut = Field(new ArrayFieldProperties()); @@ -48,7 +48,17 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_error_if_tags_are_required_and_null() + public async Task Should_not_add_error_if_number_of_items_is_equal_to_min_and_max_items() + { + var sut = Field(new ArrayFieldProperties { MinItems = 2, MaxItems = 2 }); + + await sut.ValidateAsync(CreateValue(JsonValue.Object(), JsonValue.Object()), errors); + + Assert.Empty(errors); + } + + [Fact] + public async Task Should_add_error_if_items_are_required_and_null() { var sut = Field(new ArrayFieldProperties { IsRequired = true }); @@ -59,7 +69,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent } [Fact] - public async Task Should_add_error_if_tags_are_required_and_empty() + public async Task Should_add_error_if_items_are_required_and_empty() { var sut = Field(new ArrayFieldProperties { IsRequired = true }); @@ -99,7 +109,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(JsonValue.Object(), JsonValue.Object()), errors); errors.Should().BeEquivalentTo( - new[] { "Must have not more than 1 item(s)." }); + new[] { "Must not have more than 1 item(s)." }); } private static IJsonValue CreateValue(params JsonObject[] ids) diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs index 59b670dbb..b366b9f02 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/AssetsFieldTests.cs @@ -92,6 +92,16 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent Assert.Empty(errors); } + [Fact] + public async Task Should_not_add_error_if_number_of_assets_is_equal_to_min_and_max_items() + { + var sut = Field(new AssetsFieldProperties { MinItems = 2, MaxItems = 2 }); + + await sut.ValidateAsync(CreateValue(document.AssetId, document.AssetId), errors, ctx); + + Assert.Empty(errors); + } + [Fact] public async Task Should_add_error_if_assets_are_required_and_null() { @@ -144,7 +154,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(document.AssetId, document.AssetId), errors, ctx); errors.Should().BeEquivalentTo( - new[] { "Must have not more than 1 item(s)." }); + new[] { "Must not have more than 1 item(s)." }); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs index 8d6893cdd..d6b26b049 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ContentValidationTests.cs @@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.Should().BeEquivalentTo( new List { - new ValidationError("my-field: Must be less than or equal to '100'.", "my-field") + new ValidationError("my-field: Must be less or equal to '100'.", "my-field") }); } @@ -221,7 +221,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent errors.Should().BeEquivalentTo( new List { - new ValidationError("my-field: Must be less than or equal to '100'.", "my-field") + new ValidationError("my-field: Must be less or equal to '100'.", "my-field") }); } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs index dfb26997c..29702d25e 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/DateTimeFieldTests.cs @@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(FutureDays(0)), errors); errors.Should().BeEquivalentTo( - new[] { $"Must be greater than or equal to '{FutureDays(10)}'." }); + new[] { $"Must be greater or equal to '{sut.Properties.MinValue}'." }); } [Fact] @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(FutureDays(20)), errors); errors.Should().BeEquivalentTo( - new[] { $"Must be less than or equal to '{FutureDays(10)}'." }); + new[] { $"Must be less or equal to '{FutureDays(10)}'." }); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs index 393141089..1be28f5b9 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/NumberFieldTests.cs @@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(5), errors); errors.Should().BeEquivalentTo( - new[] { "Must be greater than or equal to '10'." }); + new[] { "Must be greater or equal to '10'." }); } [Fact] @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(20), errors); errors.Should().BeEquivalentTo( - new[] { "Must be less than or equal to '10'." }); + new[] { "Must be less or equal to '10'." }); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs index 43e8ed3cf..ace515b79 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ReferencesFieldTests.cs @@ -51,6 +51,16 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent Assert.Empty(errors); } + [Fact] + public async Task Should_not_add_error_if_number_of_references_is_equal_to_min_and_max_items() + { + var sut = Field(new ReferencesFieldProperties { MinItems = 2, MaxItems = 2 }); + + await sut.ValidateAsync(CreateValue(ref1, ref2), errors); + + Assert.Empty(errors); + } + [Fact] public async Task Should_add_error_if_references_are_required_and_null() { @@ -103,7 +113,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue(ref1, ref2), errors, ValidationTestExtensions.References(ref1, ref2)); errors.Should().BeEquivalentTo( - new[] { "Must have not more than 1 item(s)." }); + new[] { "Must not have more than 1 item(s)." }); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs index 6b83628f6..653f30c74 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/StringFieldTests.cs @@ -57,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue("123"), errors); errors.Should().BeEquivalentTo( - new[] { "Must have more than '10' characters." }); + new[] { "Must have at least 10 character(s)." }); } [Fact] @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue("12345678"), errors); errors.Should().BeEquivalentTo( - new[] { "Must have less than '5' characters." }); + new[] { "Must not have more than 5 character(s)." }); } [Fact] @@ -90,7 +90,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue("abc"), errors); errors.Should().BeEquivalentTo( - new[] { "Not valid." }); + new[] { "Does not match to the pattern." }); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs index ca970755e..23eef2ef9 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/TagsFieldTests.cs @@ -48,6 +48,16 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent Assert.Empty(errors); } + [Fact] + public async Task Should_not_add_error_if_number_of_tags_is_equal_to_min_and_max_items() + { + var sut = Field(new TagsFieldProperties { MinItems = 2, MaxItems = 2 }); + + await sut.ValidateAsync(CreateValue("tag1", "tag2"), errors); + + Assert.Empty(errors); + } + [Fact] public async Task Should_add_error_if_tags_are_required_and_null() { @@ -100,7 +110,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent await sut.ValidateAsync(CreateValue("tag-1", "tag-2"), errors); errors.Should().BeEquivalentTo( - new[] { "Must have not more than 1 item(s)." }); + new[] { "Must not have more than 1 item(s)." }); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs index 3e8af9fb4..b4f67dab0 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionItemValidatorTests.cs @@ -47,8 +47,8 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators errors.Should().BeEquivalentTo( new[] { - "[2]: Must be greater than or equal to '2'.", - "[4]: Must be less than or equal to '4'." + "[2]: Must be between '2' and '4'.", + "[4]: Must be between '2' and '4'." }); } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs index 7f3301d0c..394075fb1 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/CollectionValidatorTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using System; using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; @@ -17,6 +18,13 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators { private readonly List errors = new List(); + [Theory] + [InlineData(20, 10)] + public void Should_throw_error_if_min_greater_than_max(int? min, int? max) + { + Assert.Throws(() => new CollectionValidator(true, min, max)); + } + [Fact] public async Task Should_not_add_error_if_value_is_valid() { @@ -48,10 +56,21 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators new[] { "Field is required." }); } + [Fact] + public async Task Should_add_error_if_collection_has_not_exact_number_of_items() + { + var sut = new CollectionValidator(true, 2, 2); + + await sut.ValidateAsync(new List { 1 }, errors); + + errors.Should().BeEquivalentTo( + new[] { "Must have exactly 2 item(s)." }); + } + [Fact] public async Task Should_add_error_if_collection_has_too_few_items() { - var sut = new CollectionValidator(true, 2, 3); + var sut = new CollectionValidator(true, 2, null); await sut.ValidateAsync(new List { 1 }, errors); @@ -62,12 +81,23 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators [Fact] public async Task Should_add_error_if_collection_has_too_many_items() { - var sut = new CollectionValidator(true, 2, 3); + var sut = new CollectionValidator(true, null, 3); await sut.ValidateAsync(new List { 1, 2, 3, 4 }, errors); errors.Should().BeEquivalentTo( - new[] { "Must have not more than 3 item(s)." }); + new[] { "Must not have more than 3 item(s)." }); + } + + [Fact] + public async Task Should_add_error_if_collection_count_is_not_in_range() + { + var sut = new CollectionValidator(true, 2, 5); + + await sut.ValidateAsync(new List { 1 }, errors); + + errors.Should().BeEquivalentTo( + new[] { "Must have between 2 and 5 item(s)." }); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs index 2d14038c9..ca6eaf6b2 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/PatternValidatorTests.cs @@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync("foo", errors); errors.Should().BeEquivalentTo( - new[] { "Not valid." }); + new[] { "Does not match to the pattern." }); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs index 124a9e8b8..1a976f2fe 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/RangeValidatorTests.cs @@ -44,12 +44,22 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators [Theory] [InlineData(20, 10)] - [InlineData(10, 10)] public void Should_throw_error_if_min_greater_than_max(int? min, int? max) { Assert.Throws(() => new RangeValidator(min, max)); } + [Fact] + public async Task Should_add_error_if_value_is_not_equal_to_min_and_max() + { + var sut = new RangeValidator(2000, 2000); + + await sut.ValidateAsync(1500, errors); + + errors.Should().BeEquivalentTo( + new[] { "Must be exactly '2000'." }); + } + [Fact] public async Task Should_add_error_if_value_is_smaller_than_min() { @@ -58,7 +68,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync(1500, errors); errors.Should().BeEquivalentTo( - new[] { "Must be greater than or equal to '2000'." }); + new[] { "Must be greater or equal to '2000'." }); } [Fact] @@ -69,7 +79,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync(1500, errors); errors.Should().BeEquivalentTo( - new[] { "Must be less than or equal to '1000'." }); + new[] { "Must be less or equal to '1000'." }); } } } diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs index 518549a60..032171e28 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/Validators/StringLengthValidatorTests.cs @@ -55,12 +55,22 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators [Theory] [InlineData(20, 10)] - [InlineData(10, 10)] public void Should_throw_error_if_min_greater_than_max(int? min, int? max) { Assert.Throws(() => new StringLengthValidator(min, max)); } + [Fact] + public async Task Should_add_error_if_collection_has_not_exact_number_of_items() + { + var sut = new StringLengthValidator(2000, 2000); + + await sut.ValidateAsync(CreateString(4), errors); + + errors.Should().BeEquivalentTo( + new[] { "Must have exactly 2000 character(s)." }); + } + [Fact] public async Task Should_add_error_if_value_is_smaller_than_min() { @@ -69,7 +79,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync(CreateString(1500), errors); errors.Should().BeEquivalentTo( - new[] { "Must have more than '2000' characters." }); + new[] { "Must have at least 2000 character(s)." }); } [Fact] @@ -80,7 +90,18 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent.Validators await sut.ValidateAsync(CreateString(1500), errors); errors.Should().BeEquivalentTo( - new[] { "Must have less than '1000' characters." }); + new[] { "Must not have more than 1000 character(s)." }); + } + + [Fact] + public async Task Should_add_error_if_collection_count_is_not_in_range() + { + var sut = new StringLengthValidator(2000, 5000); + + await sut.ValidateAsync(CreateString(1), errors); + + errors.Should().BeEquivalentTo( + new[] { "Must have between 2000 and 5000 character(s)." }); } private static string CreateString(int size) diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs index 1693ca46f..9cb3bd977 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppClientsTests.cs @@ -113,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var clients_1 = clients_0.Add("ios", "secret"); ValidationAssert.Throws(() => GuardAppClients.CanUpdate(clients_1, command, roles), - new ValidationError("Role is not valid.", "Role")); + new ValidationError("Role is not a valid value.", "Role")); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs index 8cc7061a9..fee3c9092 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppContributorsTests.cs @@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new AssignContributor { ContributorId = "1", Role = "Invalid" }; return ValidationAssert.ThrowsAsync(() => GuardAppContributors.CanAssign(contributors_0, command, users, appPlan, roles), - new ValidationError("Role is not valid.", "Role")); + new ValidationError("Role is not a valid value.", "Role")); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs index 85e03822f..dda270c53 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppLanguagesTests.cs @@ -90,7 +90,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var languages_1 = languages_0.Set(new LanguageConfig(Language.EN)); ValidationAssert.Throws(() => GuardAppLanguages.CanUpdate(languages_1, command), - new ValidationError("Language is required.", "Language")); + new ValidationError("Language code is required.", "Language")); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs index 68968ae72..3bb1902c1 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppPatternsTests.cs @@ -45,7 +45,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new AddPattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" }; ValidationAssert.Throws(() => GuardAppPatterns.CanAdd(patterns_0, command), - new ValidationError("Pattern is not a valid regular expression.", "Pattern")); + new ValidationError("Pattern is not a valid value.", "Pattern")); } [Fact] @@ -126,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new UpdatePattern { PatternId = patternId, Name = "any", Pattern = "[0-9{1}" }; ValidationAssert.Throws(() => GuardAppPatterns.CanUpdate(patterns_1, command), - new ValidationError("Pattern is not a valid regular expression.", "Pattern")); + new ValidationError("Pattern is not a valid value.", "Pattern")); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs index f560dfb53..b6e750a28 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Guards/GuardAppTests.cs @@ -39,7 +39,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Guards var command = new CreateApp { Name = "INVALID NAME" }; ValidationAssert.Throws(() => GuardApp.CanCreate(command), - new ValidationError("Name must be a valid slug.", "Name")); + new ValidationError("Name is not a valid slug.", "Name")); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs index 5e94f0cfc..0ac1a3a41 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs @@ -99,7 +99,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard var command = new ChangeContentStatus { Status = (Status)10 }; ValidationAssert.Throws(() => GuardContent.CanChangeContentStatus(schema, false, Status.Archived, command), - new ValidationError("Status is not valid.", "Status")); + new ValidationError("Status is not a valid value.", "Status")); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ArrayFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ArrayFieldPropertiesTests.cs index f27c7f922..c97955641 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ArrayFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ArrayFieldPropertiesTests.cs @@ -26,8 +26,18 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Max items must be greater than min items.", "MinItems", "MaxItems") + new ValidationError("Max items must be greater or equal to min items.", "MinItems", "MaxItems") }); } + + [Fact] + public void Should_not_add_error_if_min_items_equals_to_max_items() + { + var sut = new ArrayFieldProperties { MinItems = 2, MaxItems = 2 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + Assert.Empty(errors); + } } } \ No newline at end of file diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs index 8f6d724d7..432aedc47 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/AssetsFieldPropertiesTests.cs @@ -26,10 +26,20 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Max items must be greater than min items.", "MinItems", "MaxItems") + new ValidationError("Max items must be greater or equal to min items.", "MinItems", "MaxItems") }); } + [Fact] + public void Should_not_add_error_if_min_equals_to_max_items() + { + var sut = new AssetsFieldProperties { MinItems = 2, MaxItems = 2 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + Assert.Empty(errors); + } + [Fact] public void Should_add_error_if_min_width_greater_than_max_width() { @@ -40,10 +50,20 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Max width must be greater than min width.", "MinWidth", "MaxWidth") + new ValidationError("Max width must be greater or equal to min width.", "MinWidth", "MaxWidth") }); } + [Fact] + public void Should_not_add_error_if_min_width_equals_to_max_width() + { + var sut = new AssetsFieldProperties { MinWidth = 2, MaxWidth = 2 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + Assert.Empty(errors); + } + [Fact] public void Should_add_error_if_min_height_greater_than_max_height() { @@ -54,10 +74,20 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Max height must be greater than min height.", "MinHeight", "MaxHeight") + new ValidationError("Max height must be greater or equal to min height.", "MinHeight", "MaxHeight") }); } + [Fact] + public void Should_not_add_error_if_min_height_equals_to_max_height() + { + var sut = new AssetsFieldProperties { MinHeight = 2, MaxHeight = 2 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + Assert.Empty(errors); + } + [Fact] public void Should_add_error_if_min_size_greater_than_max_size() { @@ -82,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Aspect width and height is required.", "AspectWidth", "AspectHeight") + new ValidationError("If aspect width or aspect height is used both must be defined.", "AspectWidth", "AspectHeight") }); } @@ -96,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Aspect width and height is required.", "AspectWidth", "AspectHeight") + new ValidationError("If aspect width or aspect height is used both must be defined.", "AspectWidth", "AspectHeight") }); } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/DateTimeFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/DateTimeFieldPropertiesTests.cs index 6cfa3feef..2e7a22d58 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/DateTimeFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/DateTimeFieldPropertiesTests.cs @@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Default value must be greater than min value.", "DefaultValue") + new ValidationError("Default value must be greater or equal to min value.", "DefaultValue") }); } @@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Default value must be less than max value.", "DefaultValue") + new ValidationError("Default value must be less or equal to max value.", "DefaultValue") }); } @@ -98,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Calculated default value is not valid.", "CalculatedDefaultValue") + new ValidationError("Calculated default value is not a valid value.", "CalculatedDefaultValue") }); } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs index a017a3f51..566a2d9af 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/NumberFieldPropertiesTests.cs @@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Default value must be greater than min value.", "DefaultValue") + new ValidationError("Default value must be greater or equal to min value.", "DefaultValue") }); } @@ -56,7 +56,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Default value must be less than max value.", "DefaultValue") + new ValidationError("Default value must be less or equal to max value.", "DefaultValue") }); } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ReferencesFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ReferencesFieldPropertiesTests.cs index cd0a0fed9..34efa28c3 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ReferencesFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/ReferencesFieldPropertiesTests.cs @@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties public class ReferencesFieldPropertiesTests { [Fact] - public void Should_add_error_if_min_greater_than_max() + public void Should_add_error_if_min_items_greater_than_max_items() { var sut = new ReferencesFieldProperties { MinItems = 10, MaxItems = 5 }; @@ -26,8 +26,18 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Max items must be greater than min items.", "MinItems", "MaxItems") + new ValidationError("Max items must be greater or equal to min items.", "MinItems", "MaxItems") }); } + + [Fact] + public void Should_not_add_error_if_min_items_greater_equals_to_max_items() + { + var sut = new ReferencesFieldProperties { MinItems = 2, MaxItems = 2 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + Assert.Empty(errors); + } } } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs index ad6faed6a..8d9969cdf 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/StringFieldPropertiesTests.cs @@ -27,10 +27,20 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Max length must be greater than min length.", "MinLength", "MaxLength") + new ValidationError("Max length must be greater or equal to min length.", "MinLength", "MaxLength") }); } + [Fact] + public void Should_not_add_error_if_min_length_equal_to_max_length() + { + var sut = new StringFieldProperties { MinLength = 2, MaxLength = 2 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + Assert.Empty(errors); + } + [Fact] public void Should_add_error_if_allowed_values_and_max_value_is_specified() { @@ -97,7 +107,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Pattern is not a valid expression.", "Pattern") + new ValidationError("Pattern is not a valid value.", "Pattern") }); } diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs index b456d3424..99c8c0ae3 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/FieldProperties/TagsFieldPropertiesTests.cs @@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties public class TagsFieldPropertiesTests { [Fact] - public void Should_add_error_if_min_greater_than_max() + public void Should_add_error_if_min_items_greater_than_max_items() { var sut = new TagsFieldProperties { MinItems = 10, MaxItems = 5 }; @@ -26,10 +26,20 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards.FieldProperties errors.Should().BeEquivalentTo( new List { - new ValidationError("Max items must be greater than min items.", "MinItems", "MaxItems") + new ValidationError("Max items must be greater or equal to min items.", "MinItems", "MaxItems") }); } + [Fact] + public void Should_not_add_error_if_min_items_equal_to_max_items() + { + var sut = new TagsFieldProperties { MinItems = 2, MaxItems = 2 }; + + var errors = FieldPropertiesValidator.Validate(sut).ToList(); + + Assert.Empty(errors); + } + [Fact] public void Should_add_error_if_radio_button_has_no_allowed_values() { diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaFieldTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaFieldTests.cs index 3a196d3e0..a4f1aaade 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaFieldTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaFieldTests.cs @@ -214,7 +214,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new UpdateField { FieldId = 2, Properties = new StringFieldProperties { MinLength = 10, MaxLength = 5 } }; ValidationAssert.Throws(() => GuardSchemaField.CanUpdate(schema_0, command), - new ValidationError("Max length must be greater than min length.", "Properties.MinLength", "Properties.MaxLength")); + new ValidationError("Max length must be greater or equal to min length.", "Properties.MinLength", "Properties.MaxLength")); } [Fact] @@ -250,7 +250,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new AddField { Name = "field4", Properties = invalidProperties }; ValidationAssert.Throws(() => GuardSchemaField.CanAdd(schema_0, command), - new ValidationError("Max length must be greater than min length.", "Properties.MinLength", "Properties.MaxLength")); + new ValidationError("Max length must be greater or equal to min length.", "Properties.MinLength", "Properties.MaxLength")); } [Fact] @@ -268,7 +268,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards var command = new AddField { Name = "field4", Partitioning = "INVALID_PARTITIONING", Properties = validProperties }; ValidationAssert.Throws(() => GuardSchemaField.CanAdd(schema_0, command), - new ValidationError("Partitioning is not valid.", "Partitioning")); + new ValidationError("Partitioning is not a valid value.", "Partitioning")); } [Fact] diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs index 6743bc7f9..102a83295 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Guards/GuardSchemaTests.cs @@ -123,7 +123,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards }; return ValidationAssert.ThrowsAsync(() => GuardSchema.CanCreate(command, appProvider), - new ValidationError("Max length must be greater than min length.", + new ValidationError("Max length must be greater or equal to min length.", "Fields[1].Properties.MinLength", "Fields[1].Properties.MaxLength")); } @@ -147,7 +147,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards }; return ValidationAssert.ThrowsAsync(() => GuardSchema.CanCreate(command, appProvider), - new ValidationError("Field partitioning is not valid.", + new ValidationError("Partitioning is not a valid value.", "Fields[1].Partitioning")); } @@ -300,7 +300,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Guards }; return ValidationAssert.ThrowsAsync(() => GuardSchema.CanCreate(command, appProvider), - new ValidationError("Max length must be greater than min length.", + new ValidationError("Max length must be greater or equal to min length.", "Fields[1].Nested[1].Properties.MinLength", "Fields[1].Nested[1].Properties.MaxLength")); }