diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index fe18692f68..9dfd2ca6f2 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -22664,8 +22664,16 @@ } } } + }, + "401": { + "description": "The resource is protected and requires an authentication token" } - } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] } }, "/umbraco/management/api/v1/security/forgot-password/reset": { @@ -22969,16 +22977,8 @@ } } } - }, - "401": { - "description": "The resource is protected and requires an authentication token" } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] + } } }, "/umbraco/management/api/v1/server/configuration": { @@ -31027,16 +31027,8 @@ } } } - }, - "401": { - "description": "The resource is protected and requires an authentication token" } - }, - "security": [ - { - "Backoffice User": [ ] - } - ] + } } }, "/umbraco/management/api/v1/user/set-user-groups": { @@ -31230,6 +31222,62 @@ } }, "/umbraco/management/api/v1/webhook": { + "get": { + "tags": [ + "Webhook" + ], + "operationId": "GetWebhook", + "parameters": [ + { + "name": "skip", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 0 + } + }, + { + "name": "take", + "in": "query", + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PagedWebhookResponseModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PagedWebhookResponseModel" + } + }, + "text/plain": { + "schema": { + "$ref": "#/components/schemas/PagedWebhookResponseModel" + } + } + } + }, + "401": { + "description": "The resource is protected and requires an authentication token" + } + }, + "security": [ + { + "Backoffice User": [ ] + } + ] + }, "post": { "tags": [ "Webhook" @@ -34061,12 +34109,27 @@ }, "DatatypeConfigurationResponseModel": { "required": [ - "canBeChanged" + "canBeChanged", + "documentListViewId", + "mediaListViewId", + "memberListViewId" ], "type": "object", "properties": { "canBeChanged": { "$ref": "#/components/schemas/DataTypeChangeModeModel" + }, + "documentListViewId": { + "type": "string", + "format": "uuid" + }, + "mediaListViewId": { + "type": "string", + "format": "uuid" + }, + "memberListViewId": { + "type": "string", + "format": "uuid" } }, "additionalProperties": false @@ -38115,6 +38178,30 @@ }, "additionalProperties": false }, + "PagedWebhookResponseModel": { + "required": [ + "items", + "total" + ], + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "items": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/WebhookResponseModel" + } + ] + } + } + }, + "additionalProperties": false + }, "PartialViewFolderResponseModel": { "type": "object", "allOf": [ diff --git a/src/Umbraco.Core/Models/ContentEditing/LinkDisplay.cs b/src/Umbraco.Core/Models/ContentEditing/LinkDisplay.cs index 9b7bde570d..860fad4802 100644 --- a/src/Umbraco.Core/Models/ContentEditing/LinkDisplay.cs +++ b/src/Umbraco.Core/Models/ContentEditing/LinkDisplay.cs @@ -23,9 +23,19 @@ public class LinkDisplay [DataMember(Name = "trashed")] public bool Trashed { get; set; } - [DataMember(Name = "udi")] - public GuidUdi? Udi { get; set; } + [DataMember(Name = "type")] + public string? Type { get; set; } + + [DataMember(Name = "unique")] + public Guid? Unique { get; set; } [DataMember(Name = "url")] public string? Url { get; set; } + + public static class Types + { + public const string Document = "document"; + public const string Media = "media"; + public const string External = "external"; + } } diff --git a/src/Umbraco.Core/PropertyEditors/ContentPickerPropertyEditor.cs b/src/Umbraco.Core/PropertyEditors/ContentPickerPropertyEditor.cs index 61b38ad543..34d9956e88 100644 --- a/src/Umbraco.Core/PropertyEditors/ContentPickerPropertyEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ContentPickerPropertyEditor.cs @@ -1,11 +1,15 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; @@ -35,6 +39,7 @@ public class ContentPickerPropertyEditor : DataEditor internal class ContentPickerPropertyValueEditor : DataValueEditor, IDataValueReference { + public ContentPickerPropertyValueEditor( IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, @@ -58,5 +63,40 @@ public class ContentPickerPropertyEditor : DataEditor yield return new UmbracoEntityReference(udi); } } + + // starting in v14 the passed in value is always a guid, we store it as a document Udi string. Else it's an invalid value + public override object? FromEditor(ContentPropertyData editorValue, object? currentValue) => + editorValue.Value is not null + && Guid.TryParse(editorValue.Value as string, out Guid guidValue) + ? GuidUdi.Create(Constants.UdiEntityType.Document, guidValue).ToString() + : null; + + public override object? ToEditor(IProperty property, string? culture = null, string? segment = null) + { + // since our storage type is a string, we can expect the base to return a string + var stringValue = base.ToEditor(property, culture, segment) as string; + + if (stringValue.IsNullOrWhiteSpace()) + { + return null; + } + + // this string can actually be an Int value from old versions => convert to it's guid counterpart + if (int.TryParse(stringValue, out var oldInt)) + { + // todo: This is a temporary code path that should be removed ASAP + Attempt conversionAttempt = StaticServiceProvider.Instance.GetRequiredService() + .GetKeyForId(oldInt, UmbracoObjectTypes.Document); + return conversionAttempt.Success ? conversionAttempt.Result : null; + } + + // if its not an old value, it should be a udi + if (UdiParser.TryParse(stringValue, out GuidUdi? guidUdi) is false) + { + return null; + } + + return guidUdi.Guid; + } } } diff --git a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs index 3c199e58af..4ccd5399ce 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfiguration.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace Umbraco.Cms.Core.PropertyEditors; /// @@ -5,6 +7,7 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// public class MultiNodePickerConfiguration : IIgnoreUserStartNodesConfig { + [JsonPropertyName("startNode")] [ConfigurationField("startNode")] public MultiNodePickerConfigurationTreeSource? TreeSource { get; set; } diff --git a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs index ba6d605cca..b62519fd5e 100644 --- a/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs +++ b/src/Umbraco.Core/PropertyEditors/MultiNodePickerConfigurationTreeSource.cs @@ -1,4 +1,5 @@ using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace Umbraco.Cms.Core.PropertyEditors; @@ -8,15 +9,18 @@ namespace Umbraco.Cms.Core.PropertyEditors; [DataContract] public class MultiNodePickerConfigurationTreeSource { + [JsonPropertyName("type")] [DataMember(Name = "type")] public string? ObjectType { get; set; } + [JsonPropertyName("query")] [DataMember(Name = "query")] public string? StartNodeQuery { get; set; } [DataMember(Name = "dynamicRoot")] public DynamicRoot? DynamicRoot { get; set; } + [JsonPropertyName("id")] [DataMember(Name = "id")] public Udi? StartNodeId { get; set; } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs index 17890fdb53..b50aa3c776 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs @@ -1,6 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.Text.Json.Nodes; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; @@ -31,8 +32,16 @@ public class MultiNodeTreePickerPropertyEditor : DataEditor protected override IDataValueEditor CreateValueEditor() => DataValueEditorFactory.Create(Attribute!); + /// + /// At first glance, the fromEditor and toEditor methods might seem strange. + /// This is because we wanted to stop the leaking of UDI's to the frontend while not having to do database migrations + /// so we opted to, for now, translate the udi string in the database into a structured format unique to the client + /// This way, for now, no migration is needed and no changes outside of the editor logic needs to be touched to stop the leaking. + /// public class MultiNodeTreePickerPropertyValueEditor : DataValueEditor, IDataValueReference { + private readonly IJsonSerializer _jsonSerializer; + public MultiNodeTreePickerPropertyValueEditor( IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, @@ -40,6 +49,7 @@ public class MultiNodeTreePickerPropertyEditor : DataEditor DataEditorAttribute attribute) : base(shortStringHelper, jsonSerializer, ioHelper, attribute) { + _jsonSerializer = jsonSerializer; } public IEnumerable GetReferences(object? value) @@ -56,23 +66,41 @@ public class MultiNodeTreePickerPropertyEditor : DataEditor } } + + public override object? FromEditor(ContentPropertyData editorValue, object? currentValue) + => editorValue.Value is JsonArray jsonArray + ? EntityReferencesToUdis(_jsonSerializer.Deserialize>(jsonArray.ToJsonString()) ?? Enumerable.Empty()) + : null; + public override object? ToEditor(IProperty property, string? culture = null, string? segment = null) { var value = property.GetValue(culture, segment); return value is string stringValue - ? ParseValidUdis(stringValue.Split(Constants.CharArrays.Comma)) - : null; + ? UdisToEntityReferences(stringValue.Split(Constants.CharArrays.Comma)).ToArray() + : null; } - public override object? FromEditor(ContentPropertyData editorValue, object? currentValue) - => editorValue.Value is IEnumerable stringValues - ? string.Join(",", ParseValidUdis(stringValues)) - : null; + private IEnumerable UdisToEntityReferences(IEnumerable stringUdis) + { + foreach (var stringUdi in stringUdis) + { + if (UdiParser.TryParse(stringUdi, out GuidUdi? guidUdi) is false) + { + continue; + } - private string[] ParseValidUdis(IEnumerable stringValues) - => stringValues - .Select(s => UdiParser.TryParse(s, out Udi? udi) && udi is GuidUdi guidUdi ? guidUdi.ToString() : null) - .WhereNotNull() - .ToArray(); + yield return new EditorEntityReference() { Type = guidUdi.EntityType, Unique = guidUdi.Guid }; + } + } + + private string EntityReferencesToUdis(IEnumerable nodeReferences) + => string.Join(",", nodeReferences.Select(entityReference => Udi.Create(entityReference.Type, entityReference.Unique).ToString())); + + public class EditorEntityReference + { + public required string Type { get; set; } + + public required Guid Unique { get; set; } + } } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs index 7e760d0f69..c9118993e7 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs @@ -130,7 +130,9 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference Trashed = trashed, Published = published, QueryString = dto.QueryString, - Udi = udi, + Type = dto.Udi is null ? LinkDisplay.Types.External + : dto.Udi.EntityType, + Unique = dto.Udi?.Guid, Url = url ?? string.Empty, }); } @@ -169,8 +171,8 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference Name = link.Name, QueryString = link.QueryString, Target = link.Target, - Udi = link.Udi, - Url = link.Udi == null ? link.Url : null, // only save the URL for external links + Udi = TypeIsUdiBased(link) ? new GuidUdi(link.Type!, link.Unique!.Value) : null, + Url = TypeIsExternal(link) ? link.Url : null, // only save the URL for external links })); } catch (Exception ex) @@ -181,6 +183,14 @@ public class MultiUrlPickerValueEditor : DataValueEditor, IDataValueReference return base.FromEditor(editorValue, currentValue); } + private static bool TypeIsExternal(LinkDisplay link) => + link.Type is not null && link.Type.Equals(LinkDisplay.Types.External, StringComparison.InvariantCultureIgnoreCase); + + private static bool TypeIsUdiBased(LinkDisplay link) => + link.Type is not null && link.Unique is not null && + (link.Type.Equals(LinkDisplay.Types.Document, StringComparison.InvariantCultureIgnoreCase) + || link.Type.Equals(LinkDisplay.Types.Media, StringComparison.InvariantCultureIgnoreCase)); + [DataContract] public class LinkDto { diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiNodeTreePickerTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiNodeTreePickerTests.cs index e551c66af5..1f84fb7400 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiNodeTreePickerTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/MultiNodeTreePickerTests.cs @@ -1,13 +1,15 @@ -using Moq; +using System.Text.Json.Nodes; +using Moq; using NUnit.Framework; +using Org.BouncyCastle.Asn1.X500; using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Serialization; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Serialization; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; @@ -67,9 +69,11 @@ public class MultiNodeTreePickerTests [Test] public void Can_Parse_Single_Value_From_Editor() { - var value = new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString(); - var fromEditor = FromEditor(new[] { value }) as string; - Assert.AreEqual(value, fromEditor); + var value = new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()); + var editorValue = $"[{{\"type\" :\"{value.EntityType}\",\"unique\":\"{value.Guid}\"}}]"; + var fromEditor = + FromEditor(JsonNode.Parse(editorValue), jsonSerializer: new SystemTextJsonSerializer()) as string; + Assert.AreEqual(value.ToString(), fromEditor); } [Test] @@ -77,51 +81,65 @@ public class MultiNodeTreePickerTests { var values = new[] { - new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString(), - new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString(), - new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString() + new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()), + new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()), + new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()) }; - var fromEditor = FromEditor(values) as string; - Assert.AreEqual(string.Join(",", values), fromEditor); + var editorValue = + $"[{{\"type\" :\"{values[0].EntityType}\",\"unique\":\"{values[0].Guid}\"}},{{\"type\" :\"{values[1].EntityType}\",\"unique\":\"{values[1].Guid}\"}},{{\"type\" :\"{values[2].EntityType}\",\"unique\":\"{values[2].Guid}\"}}]"; + + var fromEditor = FromEditor(JsonNode.Parse(editorValue), jsonSerializer: new SystemTextJsonSerializer()) as string; + Assert.AreEqual(string.Join(",", values.Select(v => v.ToString())), fromEditor); } [Test] public void Can_Parse_Different_Entity_Types_From_Editor() { - var values = new[] + var expectedValues = new[] { - new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString(), - new GuidUdi(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(), - new GuidUdi(Constants.UdiEntityType.Member, Guid.NewGuid()).ToString() + new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()), + new GuidUdi(Constants.UdiEntityType.Media, Guid.NewGuid()), + new GuidUdi(Constants.UdiEntityType.Member, Guid.NewGuid()) }; - var fromEditor = FromEditor(values) as string; - Assert.AreEqual(string.Join(",", values), fromEditor); + var editorValue = + $"[{{\"type\" :\"{expectedValues[0].EntityType}\",\"unique\":\"{expectedValues[0].Guid}\"}},{{\"type\" :\"{expectedValues[1].EntityType}\",\"unique\":\"{expectedValues[1].Guid}\"}},{{\"type\" :\"{expectedValues[2].EntityType}\",\"unique\":\"{expectedValues[2].Guid}\"}}]"; + + var fromEditor = FromEditor(JsonNode.Parse(editorValue), jsonSerializer: new SystemTextJsonSerializer()) as string; + Assert.AreEqual(string.Join(",", expectedValues.Select(v => v.ToString())), fromEditor); } [Test] - public void Can_Skip_Invalid_Values_From_Editor() + public void From_Editor_Throws_Error_On_Invalid_Json() { var values = new[] { - new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString(), - "Invalid Value", - new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString() + new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()), + new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()) }; - var fromEditor = FromEditor(values) as string; - Assert.AreEqual(string.Join(",", values.First(), values.Last()), fromEditor); + var editorValue = + $"[{{\"type\" :\"{values[0].EntityType}\",\"unique\":\"{values[0].Guid}\"}},{{\"invalidProperty\" :\"nonsenseValue\",\"otherWeirdProperty\":\"definitelyNotAGuid\"}},{{\"type\" :\"{values[1].EntityType}\",\"unique\":\"{values[1].Guid}\"}}]"; + + Assert.Catch(() => + FromEditor(JsonNode.Parse(editorValue), jsonSerializer: new SystemTextJsonSerializer())); } [Test] public void Can_Parse_Single_Value_To_Editor() { - var value = new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString(); - var toEditor = ToEditor(value) as IEnumerable; - Assert.IsNotNull(toEditor); - Assert.AreEqual(1, toEditor.Count()); - Assert.AreEqual(value, toEditor.First()); + var value = new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()); + var toEditor = ToEditor(value.ToString()) as IEnumerable; + + Assert.Multiple(() => + { + Assert.IsNotNull(toEditor); + Assert.AreEqual(1, toEditor.Count()); + Assert.AreEqual(EditorEntityReferenceFromUdi(value).Type, toEditor.First().Type); + Assert.AreEqual(EditorEntityReferenceFromUdi(value).Unique, toEditor.First().Unique); + }); + } [Test] @@ -129,16 +147,23 @@ public class MultiNodeTreePickerTests { var values = new[] { - new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString(), - new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString(), - new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString() + new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()), + new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()), + new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()) }; - var toEditor = ToEditor(string.Join(",", values)) as IEnumerable; - Assert.IsNotNull(toEditor); - Assert.AreEqual(3, toEditor.Count()); - Assert.AreEqual(values[0], toEditor.First()); - Assert.AreEqual(values[1], toEditor.Skip(1).First()); - Assert.AreEqual(values[2], toEditor.Last()); + var toEditor = ToEditor(string.Join(",", values.Select(v => v.ToString()))) as IEnumerable; + + Assert.Multiple(() => + { + Assert.IsNotNull(toEditor); + Assert.AreEqual(3, toEditor.Count()); + Assert.AreEqual(EditorEntityReferenceFromUdi(values[0]).Type, toEditor.First().Type); + Assert.AreEqual(EditorEntityReferenceFromUdi(values[0]).Unique, toEditor.First().Unique); + Assert.AreEqual(EditorEntityReferenceFromUdi(values[1]).Type, toEditor.Skip(1).First().Type); + Assert.AreEqual(EditorEntityReferenceFromUdi(values[1]).Unique, toEditor.Skip(1).First().Unique); + Assert.AreEqual(EditorEntityReferenceFromUdi(values[2]).Type, toEditor.Last().Type); + Assert.AreEqual(EditorEntityReferenceFromUdi(values[2]).Unique, toEditor.Last().Unique); + }); } [Test] @@ -146,32 +171,46 @@ public class MultiNodeTreePickerTests { var values = new[] { - new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString(), - new GuidUdi(Constants.UdiEntityType.Media, Guid.NewGuid()).ToString(), - new GuidUdi(Constants.UdiEntityType.Member, Guid.NewGuid()).ToString() + new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()), + new GuidUdi(Constants.UdiEntityType.Media, Guid.NewGuid()), + new GuidUdi(Constants.UdiEntityType.Member, Guid.NewGuid()) }; - var toEditor = ToEditor(string.Join(",", values)) as IEnumerable; - Assert.IsNotNull(toEditor); - Assert.AreEqual(3, toEditor.Count()); - Assert.AreEqual(values[0], toEditor.First()); - Assert.AreEqual(values[1], toEditor.Skip(1).First()); - Assert.AreEqual(values[2], toEditor.Last()); + var toEditor = ToEditor(string.Join(",", values.Select(v => v.ToString()))) as IEnumerable; + Assert.Multiple(() => + { + Assert.IsNotNull(toEditor); + Assert.AreEqual(3, toEditor.Count()); + Assert.AreEqual(EditorEntityReferenceFromUdi(values[0]).Type, toEditor.First().Type); + Assert.AreEqual(EditorEntityReferenceFromUdi(values[0]).Unique, toEditor.First().Unique); + Assert.AreEqual(EditorEntityReferenceFromUdi(values[1]).Type, toEditor.Skip(1).First().Type); + Assert.AreEqual(EditorEntityReferenceFromUdi(values[1]).Unique, toEditor.Skip(1).First().Unique); + Assert.AreEqual(EditorEntityReferenceFromUdi(values[2]).Type, toEditor.Last().Type); + Assert.AreEqual(EditorEntityReferenceFromUdi(values[2]).Unique, toEditor.Last().Unique); + }); + } [Test] public void Can_Skip_Invalid_Values_To_Editor() { + var firstGuid = new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()); + var secondGuid = new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()); var values = new[] { - new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString(), + firstGuid.ToString(), "Invalid Value", - new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()).ToString() + secondGuid.ToString(), }; - var toEditor = ToEditor(string.Join(",", values)) as IEnumerable; - Assert.IsNotNull(toEditor); - Assert.AreEqual(2, toEditor.Count()); - Assert.AreEqual(values[0], toEditor.First()); - Assert.AreEqual(values[2], toEditor.Last()); + var toEditor = ToEditor(string.Join(",", values)) as IEnumerable; + Assert.Multiple(() => + { + Assert.IsNotNull(toEditor); + Assert.AreEqual(2, toEditor.Count()); + Assert.AreEqual(EditorEntityReferenceFromUdi(firstGuid).Type, toEditor.First().Type); + Assert.AreEqual(EditorEntityReferenceFromUdi(firstGuid).Unique, toEditor.First().Unique); + Assert.AreEqual(EditorEntityReferenceFromUdi(secondGuid).Type, toEditor.Last().Type); + Assert.AreEqual(EditorEntityReferenceFromUdi(secondGuid).Unique, toEditor.Last().Unique); + }); } [Test] @@ -188,26 +227,36 @@ public class MultiNodeTreePickerTests Assert.IsNull(result); } - private static object? FromEditor(object? value, int max = 0) - => CreateValueEditor().FromEditor(new ContentPropertyData(value, new MultipleTextStringConfiguration { Max = max }), null); + private static object? FromEditor(object? value, int max = 0, IJsonSerializer? jsonSerializer = null) + => CreateValueEditor(jsonSerializer) + .FromEditor(new ContentPropertyData(value, new MultipleTextStringConfiguration { Max = max }), null); - private static object? ToEditor(object? value) + private static object? ToEditor(object? value, IJsonSerializer? jsonSerializer = null) { var property = new Mock(); property .Setup(p => p.GetValue(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(value); - return CreateValueEditor().ToEditor(property.Object); + return CreateValueEditor(jsonSerializer).ToEditor(property.Object); } - private static MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor CreateValueEditor() + private static MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor CreateValueEditor( + IJsonSerializer? jsonSerializer = null) { var valueEditor = new MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor( Mock.Of(), - Mock.Of(), + jsonSerializer ?? Mock.Of(), Mock.Of(), new DataEditorAttribute("alias")); return valueEditor; } + + private static MultiNodeTreePickerPropertyEditor.MultiNodeTreePickerPropertyValueEditor.EditorEntityReference + EditorEntityReferenceFromUdi(GuidUdi udi) => + new() + { + Type = udi.EntityType, + Unique = udi.Guid, + }; }