feat(templates/ui): manage compositions from the tree
Move composition CRUD off the TemplateEdit page and onto the tree context menu, matching Aveva's Template Toolbox flow. - New ComposeIntoDialog: pick a parent template, slot name (defaults to the source template's name). - "Compose into…" on every base template's context menu (kebab + right click) opens the dialog and calls AddCompositionAsync. - "Rename…" on composition leaves opens a prompt and calls TemplateService.RenameCompositionAsync. The owning composition row AND its owned derived template are renamed atomically; duplicate slot names or derived-name collisions abort with a clear error. - "Delete" on composition leaves confirms + cascade-deletes the composition (and its derived template via DeleteCompositionAsync). - "New Derived Template" menu item renamed to "New Inheriting Template" to disambiguate from the new derive-on-compose meaning. TemplateEdit's Compositions tab, Add Composition form, and Add/DeleteComposition handlers + state fields are deleted — the tree is now the single source of truth.
This commit is contained in:
@@ -354,6 +354,45 @@ public class TemplateServiceTests
|
||||
Assert.Contains("already exists", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RenameComposition_RenamesSlotAndDerivedTemplate()
|
||||
{
|
||||
var composition = new TemplateComposition("OldSlot") { Id = 50, TemplateId = 1, ComposedTemplateId = 77 };
|
||||
var owner = new Template("Pump") { Id = 1 };
|
||||
owner.Compositions.Add(composition);
|
||||
var derived = new Template("Pump.OldSlot") { Id = 77, IsDerived = true, OwnerCompositionId = 50, ParentTemplateId = 2 };
|
||||
|
||||
_repoMock.Setup(r => r.GetTemplateCompositionByIdAsync(50, It.IsAny<CancellationToken>())).ReturnsAsync(composition);
|
||||
_repoMock.Setup(r => r.GetTemplateByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(owner);
|
||||
_repoMock.Setup(r => r.GetTemplateByIdAsync(77, It.IsAny<CancellationToken>())).ReturnsAsync(derived);
|
||||
_repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<Template> { owner, derived });
|
||||
|
||||
var result = await _service.RenameCompositionAsync(50, "NewSlot", "admin");
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal("NewSlot", result.Value.InstanceName);
|
||||
Assert.Equal("Pump.NewSlot", derived.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RenameComposition_DuplicateName_Fails()
|
||||
{
|
||||
var composition = new TemplateComposition("OldSlot") { Id = 50, TemplateId = 1, ComposedTemplateId = 77 };
|
||||
var sibling = new TemplateComposition("NewSlot") { Id = 51, TemplateId = 1, ComposedTemplateId = 78 };
|
||||
var owner = new Template("Pump") { Id = 1 };
|
||||
owner.Compositions.Add(composition);
|
||||
owner.Compositions.Add(sibling);
|
||||
|
||||
_repoMock.Setup(r => r.GetTemplateCompositionByIdAsync(50, It.IsAny<CancellationToken>())).ReturnsAsync(composition);
|
||||
_repoMock.Setup(r => r.GetTemplateByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(owner);
|
||||
|
||||
var result = await _service.RenameCompositionAsync(50, "NewSlot", "admin");
|
||||
|
||||
Assert.True(result.IsFailure);
|
||||
Assert.Contains("already exists", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteComposition_CascadesDerivedTemplate()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user