mirror of https://github.com/Squidex/squidex.git
59 changed files with 755 additions and 189 deletions
@ -0,0 +1,57 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DateTimeField.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Microsoft.OData.Edm; |
||||
|
using Microsoft.OData.Edm.Library; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using NJsonSchema; |
||||
|
using Squidex.Core.Schemas.Validators; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Core.Schemas |
||||
|
{ |
||||
|
[TypeName("DateTimeField")] |
||||
|
public sealed class DateTimeField : Field<DateTimeFieldProperties> |
||||
|
{ |
||||
|
public DateTimeField(long id, string name, DateTimeFieldProperties properties) |
||||
|
: base(id, name, properties) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override IEnumerable<IValidator> CreateValidators() |
||||
|
{ |
||||
|
if (Properties.IsRequired) |
||||
|
{ |
||||
|
yield return new RequiredValidator(); |
||||
|
} |
||||
|
|
||||
|
if (Properties.MinValue.HasValue || Properties.MaxValue.HasValue) |
||||
|
{ |
||||
|
yield return new RangeValidator<DateTimeOffset>(Properties.MinValue, Properties.MaxValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override object ConvertValue(JToken value) |
||||
|
{ |
||||
|
return (DateTimeOffset?)value; |
||||
|
} |
||||
|
|
||||
|
protected override void PrepareJsonSchema(JsonProperty jsonProperty) |
||||
|
{ |
||||
|
jsonProperty.Type = JsonObjectType.String; |
||||
|
jsonProperty.Format = "date-time"; |
||||
|
} |
||||
|
|
||||
|
protected override IEdmTypeReference CreateEdmType() |
||||
|
{ |
||||
|
return EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.DateTimeOffset, !Properties.IsRequired); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DateTimeFieldEditor.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Core.Schemas |
||||
|
{ |
||||
|
public enum DateTimeFieldEditor |
||||
|
{ |
||||
|
Date, |
||||
|
DateWithTimezone, |
||||
|
DateTime, |
||||
|
DateTimeWithTimezone |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,96 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DateTimeFieldProperties.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Core.Schemas |
||||
|
{ |
||||
|
[TypeName("DateTime")] |
||||
|
public sealed class DateTimeFieldProperties : FieldProperties |
||||
|
{ |
||||
|
private DateTimeFieldEditor editor; |
||||
|
private DateTimeOffset? maxValue; |
||||
|
private DateTimeOffset? minValue; |
||||
|
private DateTimeOffset? defaultValue; |
||||
|
|
||||
|
public DateTimeOffset? MaxValue |
||||
|
{ |
||||
|
get { return maxValue; } |
||||
|
set |
||||
|
{ |
||||
|
ThrowIfFrozen(); |
||||
|
|
||||
|
maxValue = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public DateTimeOffset? MinValue |
||||
|
{ |
||||
|
get { return minValue; } |
||||
|
set |
||||
|
{ |
||||
|
ThrowIfFrozen(); |
||||
|
|
||||
|
minValue = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public DateTimeOffset? DefaultValue |
||||
|
{ |
||||
|
get { return defaultValue; } |
||||
|
set |
||||
|
{ |
||||
|
ThrowIfFrozen(); |
||||
|
|
||||
|
defaultValue = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public DateTimeFieldEditor Editor |
||||
|
{ |
||||
|
get { return editor; } |
||||
|
set |
||||
|
{ |
||||
|
ThrowIfFrozen(); |
||||
|
|
||||
|
editor = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override JToken GetDefaultValue() |
||||
|
{ |
||||
|
return DefaultValue; |
||||
|
} |
||||
|
|
||||
|
protected override IEnumerable<ValidationError> ValidateCore() |
||||
|
{ |
||||
|
if (!Editor.IsEnumValue()) |
||||
|
{ |
||||
|
yield return new ValidationError("Editor ist not a valid value", nameof(Editor)); |
||||
|
} |
||||
|
|
||||
|
if (MaxValue.HasValue && MinValue.HasValue && MinValue.Value >= MaxValue.Value) |
||||
|
{ |
||||
|
yield return new ValidationError("Max value must be greater than min value", nameof(MinValue), nameof(MaxValue)); |
||||
|
} |
||||
|
|
||||
|
if (DefaultValue.HasValue && MinValue.HasValue && DefaultValue.Value < MinValue.Value) |
||||
|
{ |
||||
|
yield return new ValidationError("Default value must be greater than min value", nameof(DefaultValue)); |
||||
|
} |
||||
|
|
||||
|
if (DefaultValue.HasValue && MaxValue.HasValue && DefaultValue.Value > MaxValue.Value) |
||||
|
{ |
||||
|
yield return new ValidationError("Default value must be less than max value", nameof(DefaultValue)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DateTimeFieldPropertiesDto.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Immutable; |
||||
|
using Newtonsoft.Json; |
||||
|
using Newtonsoft.Json.Converters; |
||||
|
using NJsonSchema.Annotations; |
||||
|
using Squidex.Core.Schemas; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
|
||||
|
namespace Squidex.Controllers.Api.Schemas.Models |
||||
|
{ |
||||
|
[JsonSchema("dateTime")] |
||||
|
public sealed class DateTimeFieldPropertiesDto : FieldPropertiesDto |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The default value for the field value.
|
||||
|
/// </summary>
|
||||
|
public string DefaultValue { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The maximum allowed value for the field value.
|
||||
|
/// </summary>
|
||||
|
public string MaxValue { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The minimum allowed value for the field value.
|
||||
|
/// </summary>
|
||||
|
public string MinValue { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The editor that is used to manage this field.
|
||||
|
/// </summary>
|
||||
|
[JsonConverter(typeof(StringEnumConverter))] |
||||
|
public DateTimeFieldEditor Editor { get; set; } |
||||
|
|
||||
|
public override FieldProperties ToProperties() |
||||
|
{ |
||||
|
var result = SimpleMapper.Map(this, new DateTimeFieldProperties()); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,141 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DateTimeFieldPropertiesTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using FluentAssertions; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Core.Schemas |
||||
|
{ |
||||
|
public class DateTimeFieldPropertiesTests |
||||
|
{ |
||||
|
private readonly List<ValidationError> errors = new List<ValidationError>(); |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_not_add_error_if_sut_is_valid() |
||||
|
{ |
||||
|
var sut = new DateTimeFieldProperties |
||||
|
{ |
||||
|
MinValue = FutureDays(10), |
||||
|
MaxValue = FutureDays(20), |
||||
|
DefaultValue = FutureDays(15) |
||||
|
}; |
||||
|
|
||||
|
sut.Validate(errors); |
||||
|
|
||||
|
Assert.Equal(0, errors.Count); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_add_error_if_default_value_is_less_than_min() |
||||
|
{ |
||||
|
var sut = new DateTimeFieldProperties { MinValue = FutureDays(10), DefaultValue = FutureDays(5) }; |
||||
|
|
||||
|
sut.Validate(errors); |
||||
|
|
||||
|
errors.ShouldBeEquivalentTo( |
||||
|
new List<ValidationError> |
||||
|
{ |
||||
|
new ValidationError("Default value must be greater than min value", "DefaultValue") |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_add_error_if_default_value_is_greater_than_min() |
||||
|
{ |
||||
|
var sut = new DateTimeFieldProperties { MaxValue = FutureDays(10), DefaultValue = FutureDays(15) }; |
||||
|
|
||||
|
sut.Validate(errors); |
||||
|
|
||||
|
errors.ShouldBeEquivalentTo( |
||||
|
new List<ValidationError> |
||||
|
{ |
||||
|
new ValidationError("Default value must be less than max value", "DefaultValue") |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_add_error_if_min_greater_than_max() |
||||
|
{ |
||||
|
var sut = new DateTimeFieldProperties { MinValue = FutureDays(10), MaxValue = FutureDays(5) }; |
||||
|
|
||||
|
sut.Validate(errors); |
||||
|
|
||||
|
errors.ShouldBeEquivalentTo( |
||||
|
new List<ValidationError> |
||||
|
{ |
||||
|
new ValidationError("Max value must be greater than min value", "MinValue", "MaxValue") |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_add_error_if_editor_is_not_valid() |
||||
|
{ |
||||
|
var sut = new DateTimeFieldProperties { Editor = (DateTimeFieldEditor)123 }; |
||||
|
|
||||
|
sut.Validate(errors); |
||||
|
|
||||
|
errors.ShouldBeEquivalentTo( |
||||
|
new List<ValidationError> |
||||
|
{ |
||||
|
new ValidationError("Editor ist not a valid value", "Editor") |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_set_or_freeze_sut() |
||||
|
{ |
||||
|
var sut = new DateTimeFieldProperties(); |
||||
|
|
||||
|
foreach (var property in sut.GetType().GetRuntimeProperties().Where(x => x.Name != "IsFrozen")) |
||||
|
{ |
||||
|
var value = |
||||
|
property.PropertyType.GetTypeInfo().IsValueType ? |
||||
|
Activator.CreateInstance(property.PropertyType) : |
||||
|
null; |
||||
|
|
||||
|
property.SetValue(sut, value); |
||||
|
|
||||
|
var result = property.GetValue(sut); |
||||
|
|
||||
|
Assert.Equal(value, result); |
||||
|
} |
||||
|
|
||||
|
sut.Freeze(); |
||||
|
|
||||
|
foreach (var property in sut.GetType().GetRuntimeProperties().Where(x => x.Name != "IsFrozen")) |
||||
|
{ |
||||
|
var value = |
||||
|
property.PropertyType.GetTypeInfo().IsValueType ? |
||||
|
Activator.CreateInstance(property.PropertyType) : |
||||
|
null; |
||||
|
|
||||
|
Assert.Throws<InvalidOperationException>(() => |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
property.SetValue(sut, value); |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
throw ex.InnerException; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static DateTimeOffset FutureDays(int days) |
||||
|
{ |
||||
|
return DateTimeOffset.UtcNow.AddDays(days); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DateTimeFieldTests.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using FluentAssertions; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Core.Schemas |
||||
|
{ |
||||
|
public class DateTimeFieldTests |
||||
|
{ |
||||
|
private readonly List<string> errors = new List<string>(); |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_instantiate_field() |
||||
|
{ |
||||
|
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties()); |
||||
|
|
||||
|
Assert.Equal("my-datetime", sut.Name); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_clone_object() |
||||
|
{ |
||||
|
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties()); |
||||
|
|
||||
|
Assert.NotEqual(sut, sut.Enable()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_not_add_error_if_valid() |
||||
|
{ |
||||
|
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { Label = "My-DateTime" }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(null), errors); |
||||
|
|
||||
|
Assert.Empty(errors); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_errors_if_datetime_is_required() |
||||
|
{ |
||||
|
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { Label = "My-DateTime", IsRequired = true }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(null), errors); |
||||
|
|
||||
|
errors.ShouldBeEquivalentTo( |
||||
|
new[] { "My-DateTime is required" }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_errors_if_datetime_is_less_than_min() |
||||
|
{ |
||||
|
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { Label = "My-DateTime", MinValue = FutureDays(10) }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(FutureDays(0)), errors); |
||||
|
|
||||
|
errors.ShouldBeEquivalentTo( |
||||
|
new[] { $"My-DateTime must be greater than '{DateTimeOffset.UtcNow.AddDays(10)}'" }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_errors_if_datetime_is_greater_than_max() |
||||
|
{ |
||||
|
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { Label = "My-DateTime", MaxValue = FutureDays(10) }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue(FutureDays(20)), errors); |
||||
|
|
||||
|
errors.ShouldBeEquivalentTo( |
||||
|
new[] { $"My-DateTime must be less than '{FutureDays(10)}'" }); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task Should_add_errors_if_value_is_not_valid() |
||||
|
{ |
||||
|
var sut = new DateTimeField(1, "my-datetime", new DateTimeFieldProperties { Label = "My-DateTime" }); |
||||
|
|
||||
|
await sut.ValidateAsync(CreateValue("Invalid"), errors); |
||||
|
|
||||
|
errors.ShouldBeEquivalentTo( |
||||
|
new[] { "My-DateTime is not a valid value" }); |
||||
|
} |
||||
|
|
||||
|
private static DateTimeOffset FutureDays(int days) |
||||
|
{ |
||||
|
return DateTimeOffset.UtcNow.AddDays(days); |
||||
|
} |
||||
|
|
||||
|
private static JValue CreateValue(object v) |
||||
|
{ |
||||
|
return new JValue(v); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue