Content Type inheritance (#19034)

* content type nesting

* TODOs

* repository detail manager

* todo

* implement unlimited compositions

* a little refactor

* warn

* clear state

* refactor to use unique

* note

* code corrections to match with types

* unique type for Array State

* implement usedForInheritance and editedTypes for Structure Manager and Compositions

* rename method

* Update repository-details.manager.ts

* avoid type casting

* align naming

* do not await

* fix race condition when switching document types fast

* remove test prop

* Update manifests.ts

* UmbMediaTypeWorkspaceContext Routes for inheritance

* import

---------

Co-authored-by: Mads Rasmussen <madsr@hey.com>
This commit is contained in:
Niels Lyngsø
2025-04-23 15:05:32 +02:00
committed by GitHub
parent 2cf28271cd
commit b330395db0
38 changed files with 910 additions and 283 deletions

View File

@@ -544,6 +544,35 @@ internal sealed partial class ContentTypeEditingServiceTests
});
}
[Test]
public async Task Can_Create_Child_To_Content_Type_With_Composition()
{
var compositionContentType = (await ContentTypeEditingService.CreateAsync(ContentTypeCreateModel("Composition"), Constants.Security.SuperUserKey)).Result!;
var parentContentType = (await ContentTypeEditingService.CreateAsync(
ContentTypeCreateModel(
"Parent",
compositions: [new Composition { CompositionType = CompositionType.Composition, Key = compositionContentType.Key }]),
Constants.Security.SuperUserKey)).Result!;
var result = await ContentTypeEditingService.CreateAsync(
ContentTypeCreateModel(
"Child",
compositions: [new Composition { CompositionType = CompositionType.Inheritance, Key = parentContentType.Key }]),
Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
// Ensure it's actually persisted
var childContentType = await ContentTypeService.GetAsync(result.Result!.Key);
Assert.IsNotNull(childContentType);
Assert.Multiple(() =>
{
Assert.AreEqual("Child", childContentType.Name);
Assert.AreEqual(1, childContentType.ContentTypeComposition.Count());
Assert.AreEqual(parentContentType.Key, childContentType.ContentTypeComposition.Single().Key);
});
}
[Test]
public async Task Cannot_Be_Both_Parent_And_Composition()
{
@@ -691,6 +720,32 @@ internal sealed partial class ContentTypeEditingServiceTests
Assert.AreEqual(ContentTypeOperationStatus.InvalidParent, result.Status);
}
[Test]
public async Task Cannot_Have_Same_Key_For_Inheritance_And_Parent()
{
var parentModel = ContentTypeCreateModel("Parent");
var parent = (await ContentTypeEditingService.CreateAsync(parentModel, Constants.Security.SuperUserKey)).Result;
Assert.IsNotNull(parent);
Composition[] composition =
{
new()
{
CompositionType = CompositionType.Inheritance, Key = parent.Key,
}
};
var childModel = ContentTypeCreateModel(
"Child",
containerKey: parent.Key,
compositions: composition);
var result = await ContentTypeEditingService.CreateAsync(childModel, Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentTypeOperationStatus.InvalidParent, result.Status);
}
[Test]
public async Task Cannot_Use_As_ParentKey()
{
@@ -796,7 +851,7 @@ internal sealed partial class ContentTypeEditingServiceTests
[TestCase(".")]
[TestCase("-")]
[TestCase("!\"#¤%&/()=)?`")]
[TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { "System"})]
[TestCaseSource(nameof(DifferentCapitalizedAlias), new object[] { "System" })]
public async Task Cannot_Use_Invalid_Alias(string contentTypeAlias)
{
var createModel = ContentTypeCreateModel("Test", contentTypeAlias);

View File

@@ -601,6 +601,91 @@ internal sealed partial class ContentTypeEditingServiceTests
Assert.AreEqual(567, contentType.HistoryCleanup.KeepLatestVersionPerDayForDays);
}
[Test]
public async Task Can_Reapply_Compositions_For_Content_Type_With_Children()
{
var compositionContentType = (await ContentTypeEditingService.CreateAsync(ContentTypeCreateModel("Composition"), Constants.Security.SuperUserKey)).Result!;
var parentContentType = (await ContentTypeEditingService.CreateAsync(
ContentTypeCreateModel(
"Parent",
compositions: [new Composition { CompositionType = CompositionType.Composition, Key = compositionContentType.Key }]),
Constants.Security.SuperUserKey)).Result!;
var childContentType = (await ContentTypeEditingService.CreateAsync(
ContentTypeCreateModel(
"Child",
compositions: [new Composition { CompositionType = CompositionType.Inheritance, Key = parentContentType.Key }]),
Constants.Security.SuperUserKey)).Result!;
var updateModel = ContentTypeUpdateModel(
"Parent Updated",
compositions: [new() { CompositionType = CompositionType.Composition, Key = compositionContentType.Key }]);
var result = await ContentTypeEditingService.UpdateAsync(parentContentType, updateModel, Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
// Ensure it's actually persisted
parentContentType = await ContentTypeService.GetAsync(parentContentType.Key);
Assert.IsNotNull(parentContentType);
Assert.Multiple(() =>
{
Assert.AreEqual("Parent Updated", parentContentType.Name);
Assert.AreEqual(1, parentContentType.ContentTypeComposition.Count());
Assert.AreEqual(compositionContentType.Key, parentContentType.ContentTypeComposition.Single().Key);
});
childContentType = await ContentTypeService.GetAsync(childContentType.Key);
Assert.IsNotNull(childContentType);
Assert.Multiple(() =>
{
Assert.AreEqual("Child", childContentType.Name);
Assert.AreEqual(1, childContentType.ContentTypeComposition.Count());
Assert.AreEqual(parentContentType.Key, childContentType.ContentTypeComposition.Single().Key);
});
}
[Test]
public async Task Can_Remove_Compositions_For_Content_Type_With_Children()
{
var compositionContentType = (await ContentTypeEditingService.CreateAsync(ContentTypeCreateModel("Composition"), Constants.Security.SuperUserKey)).Result!;
var parentContentType = (await ContentTypeEditingService.CreateAsync(
ContentTypeCreateModel(
"Parent",
compositions: [new Composition { CompositionType = CompositionType.Composition, Key = compositionContentType.Key }]),
Constants.Security.SuperUserKey)).Result!;
var childContentType = (await ContentTypeEditingService.CreateAsync(
ContentTypeCreateModel(
"Child",
compositions: [new Composition { CompositionType = CompositionType.Inheritance, Key = parentContentType.Key }]),
Constants.Security.SuperUserKey)).Result!;
var updateModel = ContentTypeUpdateModel("Parent Updated", compositions: []);
var result = await ContentTypeEditingService.UpdateAsync(parentContentType, updateModel, Constants.Security.SuperUserKey);
Assert.IsTrue(result.Success);
// Ensure it's actually persisted
parentContentType = await ContentTypeService.GetAsync(parentContentType.Key);
Assert.IsNotNull(parentContentType);
Assert.Multiple(() =>
{
Assert.AreEqual("Parent Updated", parentContentType.Name);
Assert.IsEmpty(parentContentType.ContentTypeComposition);
});
childContentType = await ContentTypeService.GetAsync(childContentType.Key);
Assert.IsNotNull(childContentType);
Assert.Multiple(() =>
{
Assert.AreEqual("Child", childContentType.Name);
Assert.AreEqual(1, childContentType.ContentTypeComposition.Count());
Assert.AreEqual(parentContentType.Key, childContentType.ContentTypeComposition.Single().Key);
});
}
[TestCase(false)]
[TestCase(true)]
public async Task Cannot_Move_Properties_To_Non_Existing_Containers(bool isElement)
@@ -826,4 +911,44 @@ internal sealed partial class ContentTypeEditingServiceTests
Assert.IsFalse(result.Success);
Assert.AreEqual(ContentTypeOperationStatus.InvalidContainerType, result.Status);
}
[Test]
public async Task Cannot_Add_Compositions_For_Content_Type_With_Children()
{
var compositionContentType = (await ContentTypeEditingService.CreateAsync(ContentTypeCreateModel("Composition"), Constants.Security.SuperUserKey)).Result!;
var parentContentType = (await ContentTypeEditingService.CreateAsync(ContentTypeCreateModel("Parent"), Constants.Security.SuperUserKey)).Result!;
var childContentType = (await ContentTypeEditingService.CreateAsync(
ContentTypeCreateModel(
"Child",
compositions: [new Composition { CompositionType = CompositionType.Inheritance, Key = parentContentType.Key }]),
Constants.Security.SuperUserKey)).Result!;
var updateModel = ContentTypeUpdateModel(
"Parent Updated",
compositions: [new() { CompositionType = CompositionType.Composition, Key = compositionContentType.Key }]);
var result = await ContentTypeEditingService.UpdateAsync(parentContentType, updateModel, Constants.Security.SuperUserKey);
Assert.IsFalse(result.Success);
// Ensure nothing was persisted
parentContentType = await ContentTypeService.GetAsync(parentContentType.Key);
Assert.IsNotNull(parentContentType);
Assert.Multiple(() =>
{
Assert.AreEqual("Parent", parentContentType.Name);
Assert.AreEqual(0, parentContentType.ContentTypeComposition.Count());
});
childContentType = await ContentTypeService.GetAsync(childContentType.Key);
Assert.IsNotNull(childContentType);
Assert.Multiple(() =>
{
Assert.AreEqual("Child", childContentType.Name);
Assert.AreEqual(1, childContentType.ContentTypeComposition.Count());
Assert.AreEqual(parentContentType.Key, childContentType.ContentTypeComposition.Single().Key);
});
}
}