using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.ScriptAnalysis; /// /// Covers the two {{equip}}-aware editor niceties: hover on a path literal containing the /// {{equip}} token shows an "equipment-relative" note (not the "not a known configured /// tag path" warning), and completion after {{equip}}. offers the attribute leaf names. /// public sealed class EquipTokenEditorTests { /// A fake catalog whose configured paths all share an object prefix, so the attribute /// leaf names ("Source", "Other") are what {{equip}}. completion should surface. private sealed class FakeCatalog : IScriptTagCatalog { private static readonly string[] Paths = { "Mixer_001.Source", "Mixer_001.Other" }; public Task> GetPathsAsync(string? filter, CancellationToken ct) => Task.FromResult>(Paths); public Task GetTagInfoAsync(string path, CancellationToken ct) => Task.FromResult(null); public Task> GetEquipmentRelativeLeavesAsync(string? filter, CancellationToken ct) { IEnumerable leaves = new[] { "Source", "Other" }; if (!string.IsNullOrWhiteSpace(filter)) leaves = leaves.Where(n => n.StartsWith(filter, StringComparison.OrdinalIgnoreCase)); return Task.FromResult>(leaves.ToList()); } } private static readonly ScriptAnalysisService Svc = new(new FakeCatalog()); private static async Task> Items(string code, int line, int col) => (await Svc.CompleteAsync(new CompletionsRequest(code, line, col))).Items; [Fact] public async Task Completion_after_equip_dot_offers_attribute_leaves() { // caret right after the dot of "{{equip}}." — column 30 sits between the trailing dot and // the closing quote of ctx.GetTag("{{equip}}."). var items = await Items("""return ctx.GetTag("{{equip}}.").Value;""", 1, 30); items.Select(i => i.Label).ShouldContain("{{equip}}.Source"); items.ShouldAllBe(i => i.Detail == "tag path"); } [Fact] public async Task Completion_after_equip_dot_inserts_the_full_token_qualified_leaf() { var items = await Items("""return ctx.GetTag("{{equip}}.").Value;""", 1, 30); var source = items.FirstOrDefault(i => i.Label == "{{equip}}.Source"); source.ShouldNotBeNull(); source!.InsertText.ShouldBe("{{equip}}.Source"); } [Fact] public async Task Hover_on_equip_token_literal_notes_equipment_relative() { // caret inside ctx.GetTag("{{equip}}.Source") var md = (await Svc.Hover(new HoverRequest("""return ctx.GetTag("{{equip}}.Source").Value;""", 1, 24))).Markdown; md.ShouldNotBeNull(); md!.ShouldContain("Equipment-relative"); // the {{equip}} token must render literally (non-interpolated markdown segment) md.ShouldContain("{{equip}}"); md.ShouldNotContain("Not a known"); } }