diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/TemplatePathFormatter.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/TemplatePathFormatter.cs new file mode 100644 index 00000000..fb5fdfdf --- /dev/null +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Services/TemplatePathFormatter.cs @@ -0,0 +1,33 @@ +using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates; + +namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services; + +/// +/// Renders a template's location as "Folder / Subfolder / Name" for the Create +/// Instance template field and the picker footer. Pure; tolerates dangling or +/// cyclic folder ids (stops after 32 hops) so a half-migrated catalogue never +/// breaks the create page. +/// +public static class TemplatePathFormatter +{ + /// Separator placed between each folder segment and the template name. + public const string Separator = " / "; + + /// + /// Builds the display path for by walking its + /// folder chain upward through . + /// + public static string Format(Template template, IReadOnlyDictionary foldersById) + { + var parts = new List { template.Name }; + var next = template.FolderId; + var hops = 0; + while (next is int id && foldersById.TryGetValue(id, out var folder) && hops++ < 32) + { + parts.Add(folder.Name); + next = folder.ParentFolderId; + } + parts.Reverse(); + return string.Join(Separator, parts); + } +} diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Services/TemplatePathFormatterTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Services/TemplatePathFormatterTests.cs new file mode 100644 index 00000000..c3ad8e62 --- /dev/null +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Services/TemplatePathFormatterTests.cs @@ -0,0 +1,43 @@ +using ZB.MOM.WW.ScadaBridge.CentralUI.Services; +using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates; + +namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Services; + +public class TemplatePathFormatterTests +{ + private static Dictionary Folders(params TemplateFolder[] f) => f.ToDictionary(x => x.Id); + + [Fact] + public void RootTemplate_IsNameOnly() + { + var t = new Template("Motor Controller") { Id = 1, FolderId = null }; + Assert.Equal("Motor Controller", TemplatePathFormatter.Format(t, Folders())); + } + + [Fact] + public void NestedTemplate_JoinsFolderChainWithSlashes() + { + var controllers = new TemplateFolder("Controllers") { Id = 10, ParentFolderId = null }; + var conveyor = new TemplateFolder("Conveyor") { Id = 11, ParentFolderId = 10 }; + var t = new Template("MES Line Controller") { Id = 1, FolderId = 11 }; + Assert.Equal("Controllers / Conveyor / MES Line Controller", + TemplatePathFormatter.Format(t, Folders(controllers, conveyor))); + } + + [Fact] + public void UnknownFolderId_FallsBackToNameOnly_WithoutThrowing() + { + var t = new Template("Orphan") { Id = 1, FolderId = 999 }; + Assert.Equal("Orphan", TemplatePathFormatter.Format(t, Folders())); + } + + [Fact] + public void FolderCycle_Terminates() + { + var a = new TemplateFolder("A") { Id = 1, ParentFolderId = 2 }; + var b = new TemplateFolder("B") { Id = 2, ParentFolderId = 1 }; + var t = new Template("T") { Id = 1, FolderId = 1 }; + var s = TemplatePathFormatter.Format(t, Folders(a, b)); + Assert.EndsWith("T", s); // no hang, no exception + } +}