Merge branch 'main' into v17/dev
This commit is contained in:
@@ -51,6 +51,7 @@ public static class UmbracoBuilderExtensions
|
||||
});
|
||||
builder.Services.AddSingleton<IRequestCultureService, RequestCultureService>();
|
||||
builder.Services.AddSingleton<IRequestSegmmentService, RequestSegmentService>();
|
||||
builder.Services.AddSingleton<IRequestSegmentService, RequestSegmentService>();
|
||||
builder.Services.AddSingleton<IRequestRoutingService, RequestRoutingService>();
|
||||
builder.Services.AddSingleton<IRequestRedirectService, RequestRedirectService>();
|
||||
builder.Services.AddSingleton<IRequestPreviewService, RequestPreviewService>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Umbraco.Cms.Core.DeliveryApi;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
@@ -16,12 +16,12 @@ internal sealed class ContextualizeFromAcceptHeadersAttribute : TypeFilterAttrib
|
||||
private sealed class LocalizeFromAcceptLanguageHeaderAttributeFilter : IActionFilter
|
||||
{
|
||||
private readonly IRequestCultureService _requestCultureService;
|
||||
private readonly IRequestSegmmentService _requestSegmentService;
|
||||
private readonly IRequestSegmentService _requestSegmentService;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
|
||||
public LocalizeFromAcceptLanguageHeaderAttributeFilter(
|
||||
IRequestCultureService requestCultureService,
|
||||
IRequestSegmmentService requestSegmentService,
|
||||
IRequestSegmentService requestSegmentService,
|
||||
IVariationContextAccessor variationContextAccessor)
|
||||
{
|
||||
_requestCultureService = requestCultureService;
|
||||
|
||||
@@ -3,7 +3,7 @@ using Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
namespace Umbraco.Cms.Api.Delivery.Services;
|
||||
|
||||
internal sealed class RequestSegmentService : RequestHeaderHandler, IRequestSegmmentService
|
||||
internal sealed class RequestSegmentService : RequestHeaderHandler, IRequestSegmentService, IRequestSegmmentService
|
||||
{
|
||||
public RequestSegmentService(IHttpContextAccessor httpContextAccessor)
|
||||
: base(httpContextAccessor)
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
|
||||
using Umbraco.Cms.Api.Management.ViewModels.Segment;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Actions;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Security.Authorization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.OperationStatus;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.Controllers.Document;
|
||||
|
||||
[Obsolete("This controller is temporary and will be removed in a future release (planned for v20). A more permanent solution will follow.")]
|
||||
[ApiVersion("1.0")]
|
||||
public class AvailableSegmentsController : DocumentControllerBase
|
||||
{
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly ISegmentService _segmentService;
|
||||
private readonly IUmbracoMapper _umbracoMapper;
|
||||
|
||||
public AvailableSegmentsController(
|
||||
IAuthorizationService authorizationService,
|
||||
ISegmentService segmentService,
|
||||
IUmbracoMapper umbracoMapper)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_segmentService = segmentService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/available-segment-options")]
|
||||
[MapToApiVersion("1.0")]
|
||||
[ProducesResponseType(typeof(PagedViewModel<SegmentResponseModel>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> GetAvailableSegmentOptions(
|
||||
Guid id,
|
||||
CancellationToken cancellationToken,
|
||||
int skip = 0,
|
||||
int take = 100)
|
||||
{
|
||||
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeResourceAsync(
|
||||
User,
|
||||
ContentPermissionResource.WithKeys(ActionBrowse.ActionLetter, id),
|
||||
AuthorizationPolicies.ContentPermissionByResource);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Forbidden();
|
||||
}
|
||||
|
||||
Attempt<PagedModel<Core.Models.Segment>?, SegmentOperationStatus> pagedAttempt =
|
||||
await _segmentService.GetPagedSegmentsForDocumentAsync(id, skip, take);
|
||||
|
||||
if (pagedAttempt.Success is false)
|
||||
{
|
||||
return MapFailure(pagedAttempt.Status);
|
||||
}
|
||||
|
||||
var viewModel = new PagedViewModel<SegmentResponseModel>
|
||||
{
|
||||
Items = _umbracoMapper.MapEnumerable<Core.Models.Segment, SegmentResponseModel>(pagedAttempt.Result!.Items),
|
||||
Total = pagedAttempt.Result!.Total,
|
||||
};
|
||||
|
||||
return Ok(viewModel);
|
||||
}
|
||||
|
||||
private IActionResult MapFailure(SegmentOperationStatus status)
|
||||
=> OperationStatusResult(
|
||||
status,
|
||||
problemDetailsBuilder => status switch
|
||||
{
|
||||
_ => StatusCode(
|
||||
StatusCodes.Status500InternalServerError,
|
||||
problemDetailsBuilder
|
||||
.WithTitle("Unknown segment operation status.")
|
||||
.Build()),
|
||||
});
|
||||
}
|
||||
@@ -5,12 +5,15 @@ namespace Umbraco.Cms.Api.Management.Mapping.Segment;
|
||||
|
||||
public class SegmentMapDefinition : IMapDefinition
|
||||
{
|
||||
public void DefineMaps(IUmbracoMapper mapper) => mapper.Define<Core.Models.Segment, SegmentResponseModel>((_, _) => new SegmentResponseModel { Name = string.Empty, Alias = string.Empty }, Map);
|
||||
public void DefineMaps(IUmbracoMapper mapper) => mapper.Define<Core.Models.Segment, SegmentResponseModel>(
|
||||
(_, _) => new SegmentResponseModel { Name = string.Empty, Alias = string.Empty, Cultures = null },
|
||||
Map);
|
||||
|
||||
// Umbraco.Code.MapAll
|
||||
private static void Map(Core.Models.Segment source, SegmentResponseModel target, MapperContext context)
|
||||
{
|
||||
target.Name = source.Name;
|
||||
target.Alias = source.Alias;
|
||||
target.Cultures = source.Cultures;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Umbraco.Cms.Api.Management.ViewModels.Segment;
|
||||
|
||||
public class SegmentResponseModel
|
||||
@@ -7,4 +5,7 @@ public class SegmentResponseModel
|
||||
public required string Name { get; set; } = string.Empty;
|
||||
|
||||
public required string Alias { get; set; } = string.Empty;
|
||||
|
||||
[Obsolete("This property is temporary and will be removed in a future release (planned for v20). A more permanent solution will follow.")]
|
||||
public IEnumerable<string>? Cultures { get; set; } = null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
namespace Umbraco.Cms.Core.DeliveryApi;
|
||||
|
||||
public interface IRequestSegmentService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the requested segment from the "Accept-Segment" header, if present.
|
||||
/// </summary>
|
||||
string? GetRequestedSegment();
|
||||
}
|
||||
|
||||
[Obsolete("This interface is misspelled and will be removed in Umbraco 18. Please use the correct one IRequestSegmentService")]
|
||||
public interface IRequestSegmmentService
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -5,4 +5,7 @@ public class Segment
|
||||
public required string Name { get; set; }
|
||||
|
||||
public required string Alias { get; set; }
|
||||
|
||||
[Obsolete("This property is temporary and will be removed in a future release (planned for v20). A more permanent solution will follow.")]
|
||||
public IEnumerable<string>? Cultures { get; set; } = null;
|
||||
}
|
||||
|
||||
@@ -5,5 +5,27 @@ namespace Umbraco.Cms.Core.Services;
|
||||
|
||||
public interface ISegmentService
|
||||
{
|
||||
Task<Attempt<PagedModel<Segment>?, SegmentOperationStatus>> GetPagedSegmentsAsync(int skip = 0, int take = 100);
|
||||
/// <summary>
|
||||
/// Gets a paged list of segments.
|
||||
/// </summary>
|
||||
/// <param name="skip">The number of items to skip.</param>
|
||||
/// <param name="take">The number of items to take.</param>
|
||||
/// <returns>The paged list of segments.</returns>
|
||||
Task<Attempt<PagedModel<Segment>?, SegmentOperationStatus>> GetPagedSegmentsAsync(
|
||||
int skip = 0,
|
||||
int take = 100);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a paged list of segments for a specific document.
|
||||
/// </summary>
|
||||
/// <param name="id">The document unique identifier.</param>
|
||||
/// <param name="skip">The number of items to skip.</param>
|
||||
/// <param name="take">The number of items to take.</param>
|
||||
/// <returns>The paged list of segments.</returns>
|
||||
[Obsolete("This method is temporary and will be removed in a future release (planned for v20). A more permanent solution will follow.")]
|
||||
Task<Attempt<PagedModel<Segment>?, SegmentOperationStatus>> GetPagedSegmentsForDocumentAsync(
|
||||
Guid id,
|
||||
int skip = 0,
|
||||
int take = 100)
|
||||
=> GetPagedSegmentsAsync(skip, take);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Data.SqlTypes;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
@@ -25,8 +33,91 @@ public class DateTimePropertyEditor : DataEditor
|
||||
/// <inheritdoc />
|
||||
protected override IDataValueEditor CreateValueEditor()
|
||||
{
|
||||
IDataValueEditor editor = base.CreateValueEditor();
|
||||
DateTimePropertyValueEditor editor = DataValueEditorFactory.Create<DateTimePropertyValueEditor>(Attribute!);
|
||||
editor.Validators.Add(new DateTimeValidator());
|
||||
return editor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a value editor for the datetime property editor.
|
||||
/// </summary>
|
||||
internal sealed class DateTimePropertyValueEditor : DataValueEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// The key used to retrieve the date format from the data type configuration.
|
||||
/// </summary>
|
||||
internal const string DateTypeConfigurationFormatKey = "format";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimePropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
public DateTimePropertyValueEditor(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute)
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
|
||||
{
|
||||
if (editorValue.Value is null)
|
||||
{
|
||||
return base.FromEditor(editorValue, currentValue);
|
||||
}
|
||||
|
||||
if (TryGetConfiguredDateTimeFormat(editorValue, out string? format) is false)
|
||||
{
|
||||
return base.FromEditor(editorValue, currentValue);
|
||||
}
|
||||
|
||||
if (IsTimeOnlyFormat(format) is false)
|
||||
{
|
||||
return base.FromEditor(editorValue, currentValue);
|
||||
}
|
||||
|
||||
// We have a time-only format, so we need to ensure the date part is valid for SQL Server, so we can persist
|
||||
// without error.
|
||||
// If we have a date part that is less than the minimum date, we need to adjust it to be the minimum date.
|
||||
Attempt<object?> dateConvertAttempt = editorValue.Value.TryConvertTo(typeof(DateTime?));
|
||||
if (dateConvertAttempt.Success is false || dateConvertAttempt.Result is null)
|
||||
{
|
||||
return base.FromEditor(editorValue, currentValue);
|
||||
}
|
||||
|
||||
var dateTimeValue = (DateTime)dateConvertAttempt.Result;
|
||||
int yearValue = dateTimeValue.Year > SqlDateTime.MinValue.Value.Year
|
||||
? dateTimeValue.Year
|
||||
: SqlDateTime.MinValue.Value.Year;
|
||||
return new DateTime(yearValue, dateTimeValue.Month, dateTimeValue.Day, dateTimeValue.Hour, dateTimeValue.Minute, dateTimeValue.Second);
|
||||
}
|
||||
|
||||
private static bool TryGetConfiguredDateTimeFormat(ContentPropertyData editorValue, [NotNullWhen(true)] out string? format)
|
||||
{
|
||||
if (editorValue.DataTypeConfiguration is not Dictionary<string, object> dataTypeConfigurationDictionary)
|
||||
{
|
||||
format = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyValuePair<string, object> keyValuePair = dataTypeConfigurationDictionary
|
||||
.FirstOrDefault(kvp => kvp.Key is "format");
|
||||
format = keyValuePair.Value as string;
|
||||
return string.IsNullOrWhiteSpace(format) is false;
|
||||
}
|
||||
|
||||
private static bool IsTimeOnlyFormat(string format)
|
||||
{
|
||||
DateTime testDate = DateTime.UtcNow;
|
||||
var testDateFormatted = testDate.ToString(format);
|
||||
if (DateTime.TryParseExact(testDateFormatted, format, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out DateTime parsedDate) is false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return parsedDate.Year == 1 && parsedDate.Month == 1 && parsedDate.Day == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,10 @@ export class UmbPropertyEditorUIDropdownElement
|
||||
} else {
|
||||
this.addFormControlElement(this.shadowRoot!.querySelector('umb-input-dropdown-list')!);
|
||||
}
|
||||
|
||||
if (!this.mandatory && !this._multiple) {
|
||||
this._options.unshift({ name: '', value: '', selected: false, invalid: false });
|
||||
}
|
||||
}
|
||||
|
||||
#onChange(event: CustomEvent & { target: UmbInputDropdownListElement }) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"name": "acceptancetest",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "^2.0.37",
|
||||
"@umbraco/json-models-builders": "^2.0.38",
|
||||
"@umbraco/playwright-testhelpers": "^16.0.42",
|
||||
"camelize": "^1.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"typescript": "^4.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "^2.0.37",
|
||||
"@umbraco/json-models-builders": "^2.0.38",
|
||||
"@umbraco/playwright-testhelpers": "^16.0.42",
|
||||
"camelize": "^1.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
|
||||
@@ -172,5 +172,4 @@ test('can remove content picker in the content', async ({umbracoApi, umbracoUi})
|
||||
await umbracoUi.content.isSuccessStateVisibleForSaveButton();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values).toEqual([]);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,183 @@
|
||||
import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
const contentName = 'TestContent';
|
||||
const documentTypeName = 'TestDocumentTypeForContent';
|
||||
const customDataTypeName = 'CustomMultiNodeTreePicker';
|
||||
|
||||
test.beforeEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
});
|
||||
|
||||
test.afterEach(async ({umbracoApi}) => {
|
||||
await umbracoApi.document.ensureNameNotExists(contentName);
|
||||
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
|
||||
await umbracoApi.dataType.ensureNameNotExists(customDataTypeName);
|
||||
});
|
||||
|
||||
test('can create content with content picker with allowed types', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const allowedContentPickerDocumentTypeName = 'ContentPickerDocumentType';
|
||||
const allowedContentPickerName = 'Test Content Picker';
|
||||
const notAllowedContentPickerName = 'Not Allowed Test Content Picker';
|
||||
const allowedContentPickerDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(allowedContentPickerDocumentTypeName);
|
||||
const allowedContentPickerId = await umbracoApi.document.createDefaultDocument(allowedContentPickerName, allowedContentPickerDocumentTypeId);
|
||||
// Create a custom content picker with predefined allowed types
|
||||
const customDataTypeId = await umbracoApi.dataType.createMultiNodeTreePickerDataTypeWithAllowedTypes(customDataTypeName, allowedContentPickerDocumentTypeId);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(notAllowedContentPickerName, documentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuAtRoot();
|
||||
await umbracoUi.content.clickCreateActionMenuOption();
|
||||
await umbracoUi.content.chooseDocumentType(documentTypeName);
|
||||
await umbracoUi.content.enterContentName(contentName);
|
||||
await umbracoUi.content.clickChooseButton();
|
||||
await umbracoUi.content.isModalMenuItemWithNameDisabled(notAllowedContentPickerName);
|
||||
await umbracoUi.content.selectLinkByName(allowedContentPickerName);
|
||||
await umbracoUi.content.clickChooseModalButton();
|
||||
await umbracoUi.content.clickSaveAndPublishButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published);
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values[0].value[0]['unique']).toEqual(allowedContentPickerId);
|
||||
expect(contentData.values[0].value[0]['type']).toEqual('document');
|
||||
|
||||
// Clean
|
||||
await umbracoApi.document.ensureNameNotExists(allowedContentPickerName);
|
||||
await umbracoApi.document.ensureNameNotExists(notAllowedContentPickerName);
|
||||
});
|
||||
|
||||
test('can search and see only allowed content types', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const allowedContentPickerDocumentTypeName = 'ContentPickerDocumentType';
|
||||
const allowedContentPickerName = 'Test Content Picker';
|
||||
const notAllowedContentPickerName = 'Not Allowed Test Content Picker';
|
||||
const allowedContentPickerDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(allowedContentPickerDocumentTypeName);
|
||||
const allowedContentPickerId = await umbracoApi.document.createDefaultDocument(allowedContentPickerName, allowedContentPickerDocumentTypeId);
|
||||
// Create a content with custom content picker with predefined allowed types
|
||||
const customDataTypeId = await umbracoApi.dataType.createMultiNodeTreePickerDataTypeWithAllowedTypes(customDataTypeName, allowedContentPickerDocumentTypeId);
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(notAllowedContentPickerName, documentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.clickChooseButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isModalMenuItemWithNameVisible(allowedContentPickerName);
|
||||
await umbracoUi.content.isModalMenuItemWithNameDisabled(notAllowedContentPickerName);
|
||||
await umbracoUi.content.enterSearchKeywordInTreePickerModal('Picker');
|
||||
await umbracoUi.content.isModalMenuItemWithNameVisible(notAllowedContentPickerName, false);
|
||||
await umbracoUi.content.clickEntityItemByName(allowedContentPickerName);
|
||||
await umbracoUi.content.clickChooseModalButton();
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isSuccessStateIconVisible();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values[0].value[0]['unique']).toEqual(allowedContentPickerId);
|
||||
expect(contentData.values[0].value[0]['type']).toEqual('document');
|
||||
|
||||
// Clean
|
||||
await umbracoApi.document.ensureNameNotExists(allowedContentPickerName);
|
||||
await umbracoApi.document.ensureNameNotExists(notAllowedContentPickerName);
|
||||
});
|
||||
|
||||
test('can search and see only allowed media types', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
const allowedMediaPickerName = 'Test Image';
|
||||
const notAllowedMediaPickerName = 'Test Article';
|
||||
const allowedMediaPickerId = await umbracoApi.media.createDefaultMediaWithImage(allowedMediaPickerName);
|
||||
await umbracoApi.media.createDefaultMediaWithArticle(notAllowedMediaPickerName);
|
||||
const imageMediaTypeData = await umbracoApi.mediaType.getByName('Image');
|
||||
// Create a content with custom tree picker with predefined allowed media types
|
||||
const customDataTypeId = await umbracoApi.dataType.createMultiNodeTreePickerDataTypeWithAllowedTypes(customDataTypeName, imageMediaTypeData.id, 'media');
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.clickChooseButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isMediaCardItemWithNameDisabled(notAllowedMediaPickerName);
|
||||
await umbracoUi.content.isMediaCardItemWithNameVisible(allowedMediaPickerName);
|
||||
await umbracoUi.content.enterSearchKeywordInMediaPickerModal('Test');
|
||||
await umbracoUi.content.isMediaCardItemWithNameVisible(notAllowedMediaPickerName, false);
|
||||
await umbracoUi.content.clickMediaWithName(allowedMediaPickerName);
|
||||
await umbracoUi.content.clickChooseModalButton();
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isSuccessStateIconVisible();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values[0].value[0]['unique']).toEqual(allowedMediaPickerId);
|
||||
expect(contentData.values[0].value[0]['type']).toEqual('media');
|
||||
|
||||
// Clean
|
||||
await umbracoApi.media.ensureNameNotExists(allowedMediaPickerName);
|
||||
await umbracoApi.media.ensureNameNotExists(notAllowedMediaPickerName);
|
||||
});
|
||||
|
||||
test('can search and see only allowed member types', async ({umbracoApi, umbracoUi}) => {
|
||||
// Arrange
|
||||
// Not allowed member type
|
||||
const notAllowedMemberTypeName = 'Not Allowed Member Type';
|
||||
const notAllowedMemberTypeId = await umbracoApi.memberType.createDefaultMemberType(notAllowedMemberTypeName);
|
||||
// Allowed member type
|
||||
const allowedMemberTypeData = await umbracoApi.memberType.getByName('Member');
|
||||
// Allowed member
|
||||
const allowedTestMember = {
|
||||
name : 'Allowed Test Member',
|
||||
username : 'allowedTestMember',
|
||||
email : 'allowedTestMember@acceptance.test',
|
||||
password : '0123456789',
|
||||
};
|
||||
const allowedTestMemberId = await umbracoApi.member.createDefaultMember(allowedTestMember.name, allowedMemberTypeData.id, allowedTestMember.email, allowedTestMember.username, allowedTestMember.password);
|
||||
// Not allowed member
|
||||
const notAllowedTestMember = {
|
||||
name : 'Not Allowed Test Member',
|
||||
username : 'notAllowedTestMember',
|
||||
email : 'notAllowedTestMember@acceptance.test',
|
||||
password : '0123456789',
|
||||
};
|
||||
await umbracoApi.member.createDefaultMember(notAllowedTestMember.name, notAllowedMemberTypeId, notAllowedTestMember.email, notAllowedTestMember.username, notAllowedTestMember.password);
|
||||
// Create a content with custom tree picker with predefined allowed member types
|
||||
const customDataTypeId = await umbracoApi.dataType.createMultiNodeTreePickerDataTypeWithAllowedTypes(customDataTypeName, allowedMemberTypeData.id, 'member');
|
||||
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
|
||||
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
|
||||
await umbracoUi.goToBackOffice();
|
||||
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
|
||||
|
||||
// Act
|
||||
await umbracoUi.content.goToContentWithName(contentName);
|
||||
await umbracoUi.content.clickChooseButton();
|
||||
|
||||
// Assert
|
||||
await umbracoUi.content.isModalMenuItemWithNameVisible(allowedTestMember.name);
|
||||
await umbracoUi.content.isModalMenuItemWithNameDisabled(notAllowedTestMember.name);
|
||||
await umbracoUi.content.enterSearchKeywordInMemberPickerModal('Allowed Test Member');
|
||||
await umbracoUi.content.isModalMenuItemWithNameVisible(notAllowedTestMember.name, false);
|
||||
await umbracoUi.content.clickEntityItemByName(allowedTestMember.name);
|
||||
await umbracoUi.content.clickChooseModalButton();
|
||||
await umbracoUi.content.clickSaveButton();
|
||||
await umbracoUi.content.isSuccessStateIconVisible();
|
||||
const contentData = await umbracoApi.document.getByName(contentName);
|
||||
expect(contentData.values[0].value[0]['unique']).toEqual(allowedTestMemberId);
|
||||
expect(contentData.values[0].value[0]['type']).toEqual('member');
|
||||
|
||||
// Clean
|
||||
await umbracoApi.member.ensureNameNotExists(allowedTestMember.name);
|
||||
await umbracoApi.member.ensureNameNotExists(notAllowedTestMember.name);
|
||||
await umbracoApi.memberType.ensureNameNotExists(notAllowedMemberTypeName);
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Infrastructure.PropertyEditors;
|
||||
|
||||
public class DateTimePropertyEditorTests
|
||||
{
|
||||
// Various time formats with years below the minimum, so we expect to increase the date to the minimum supported by SQL Server.
|
||||
[TestCase("01/01/0001 10:00", "01/01/1753 10:00", "hh:mm")]
|
||||
[TestCase("01/01/0001 10:00", "01/01/1753 10:00", "HH:mm")]
|
||||
[TestCase("01/01/0001 10:00", "01/01/1753 10:00", "hh mm")]
|
||||
[TestCase("10/10/1000 10:00", "10/10/1753 10:00", "hh:mm:ss")]
|
||||
[TestCase("10/10/1000 10:00", "10/10/1753 10:00", "hh-mm-ss")]
|
||||
|
||||
// Time format with year above the minimum, so we expect to not convert.
|
||||
[TestCase("01/01/2000 10:00", "01/01/2000 10:00", "HH:mm")]
|
||||
|
||||
// Date formats, so we don't convert even if the year is below the minimum.
|
||||
[TestCase("01/01/0001 10:00", "01/01/0001 10:00", "dd-MM-yyyy hh:mm")]
|
||||
[TestCase("01/01/0001 10:00", "01/01/0001 10:00", "dd-MM-yyyy")]
|
||||
[TestCase("01/01/0001 10:00", "01/01/0001 10:00", "yyyy-MM-d")]
|
||||
public void Time_Only_Format_Ensures_DateTime_Can_Be_Persisted(DateTime actualDateTime, DateTime expectedDateTime, string format)
|
||||
{
|
||||
var dateTimePropertyEditor = new DateTimePropertyEditor.DateTimePropertyValueEditor(
|
||||
Mock.Of<IShortStringHelper>(),
|
||||
Mock.Of<IJsonSerializer>(),
|
||||
Mock.Of<IIOHelper>(),
|
||||
new DataEditorAttribute("Alias") { ValueType = ValueTypes.DateTime.ToString() });
|
||||
|
||||
Dictionary<string, object> dictionary = new Dictionary<string, object> { { DateTimePropertyEditor.DateTimePropertyValueEditor.DateTypeConfigurationFormatKey, format } };
|
||||
ContentPropertyData propertyData = new ContentPropertyData(actualDateTime, dictionary);
|
||||
var value = (DateTime)dateTimePropertyEditor.FromEditor(propertyData, null);
|
||||
|
||||
Assert.AreEqual(expectedDateTime, value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user