feat(centralui): TemplatePathFormatter — folder-path label for template pickers
Claude-Session: https://claude.ai/code/session_01G2GHPnbbKGvyyfbr2XBRVH
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public static class TemplatePathFormatter
|
||||||
|
{
|
||||||
|
/// <summary>Separator placed between each folder segment and the template name.</summary>
|
||||||
|
public const string Separator = " / ";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds the display path for <paramref name="template"/> by walking its
|
||||||
|
/// folder chain upward through <paramref name="foldersById"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static string Format(Template template, IReadOnlyDictionary<int, TemplateFolder> foldersById)
|
||||||
|
{
|
||||||
|
var parts = new List<string> { 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<int, TemplateFolder> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user