feat(adminui): tag-path hover (tag kind/type/driver inside ctx.GetTag literals)
v2-ci / build (push) Failing after 47s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 47s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
This commit is contained in:
@@ -8,16 +8,26 @@ public sealed class HoverSignatureTests
|
||||
{
|
||||
private static readonly ScriptAnalysisService Svc = new();
|
||||
|
||||
[Fact] public void Hover_on_GetTag_returns_member_markdown()
|
||||
/// <summary>A fake catalog that resolves only "Line1.Speed" (Tag/Double/driver) and nothing else,
|
||||
/// so the tag-path hover branch can be exercised without a database.</summary>
|
||||
private sealed class FakeCatalog : IScriptTagCatalog
|
||||
{
|
||||
public Task<IReadOnlyList<string>> GetPathsAsync(string? f, CancellationToken ct)
|
||||
=> Task.FromResult<IReadOnlyList<string>>(System.Array.Empty<string>());
|
||||
public Task<ScriptTagInfo?> GetTagInfoAsync(string path, CancellationToken ct)
|
||||
=> Task.FromResult(path == "Line1.Speed" ? new ScriptTagInfo("Line1.Speed", "Tag", "Double", "MAIN-modbus") : null);
|
||||
}
|
||||
|
||||
[Fact] public async Task Hover_on_GetTag_returns_member_markdown()
|
||||
{
|
||||
// hover over the "GetTag" identifier in ctx.GetTag("A")
|
||||
var md = Svc.Hover(new HoverRequest("""return ctx.GetTag("A").Value;""", 1, 16)).Markdown;
|
||||
var md = (await Svc.Hover(new HoverRequest("""return ctx.GetTag("A").Value;""", 1, 16))).Markdown;
|
||||
md.ShouldNotBeNull();
|
||||
md!.ShouldContain("GetTag");
|
||||
}
|
||||
|
||||
[Fact] public void Hover_on_nothing_returns_null()
|
||||
=> Svc.Hover(new HoverRequest(" ", 1, 1)).Markdown.ShouldBeNull();
|
||||
[Fact] public async Task Hover_on_nothing_returns_null()
|
||||
=> (await Svc.Hover(new HoverRequest(" ", 1, 1))).Markdown.ShouldBeNull();
|
||||
|
||||
[Fact] public void SignatureHelp_inside_GetTag_call_shows_the_signature()
|
||||
{
|
||||
@@ -29,10 +39,10 @@ public sealed class HoverSignatureTests
|
||||
sh.Parameters!.ShouldContain(p => p.Label.Contains("path") || p.Label.Contains("string"));
|
||||
}
|
||||
|
||||
[Fact] public void Hover_on_a_plain_local_resolves_the_local_not_an_enclosing_member()
|
||||
[Fact] public async Task Hover_on_a_plain_local_resolves_the_local_not_an_enclosing_member()
|
||||
{
|
||||
// hovering the local `x` (not a member) must resolve the LOCAL, and must NOT climb to GetTag/Value.
|
||||
var md = Svc.Hover(new HoverRequest("var x = 1;\nreturn ctx.GetTag(\"A\").Value + x;", 2, 33)).Markdown;
|
||||
var md = (await Svc.Hover(new HoverRequest("var x = 1;\nreturn ctx.GetTag(\"A\").Value + x;", 2, 33))).Markdown;
|
||||
md.ShouldNotBeNull();
|
||||
md!.ShouldContain("x");
|
||||
md.ShouldNotContain("GetTag");
|
||||
@@ -46,6 +56,29 @@ public sealed class HoverSignatureTests
|
||||
sh.ActiveParameter.ShouldBe(0); // clamped; no exception
|
||||
}
|
||||
|
||||
[Fact] public void Hover_with_out_of_range_position_returns_null_without_throwing()
|
||||
=> Svc.Hover(new HoverRequest("return 1;", 99, 99)).Markdown.ShouldBeNull();
|
||||
[Fact] public async Task Hover_with_out_of_range_position_returns_null_without_throwing()
|
||||
=> (await Svc.Hover(new HoverRequest("return 1;", 99, 99))).Markdown.ShouldBeNull();
|
||||
|
||||
// ── tag-path hover (driven by the catalog) ─────────────────────────────────────────────────
|
||||
|
||||
[Fact] public async Task Hover_on_known_tag_path_literal_shows_kind_type_and_path()
|
||||
{
|
||||
var svc = new ScriptAnalysisService(new FakeCatalog());
|
||||
// caret on the path literal inside ctx.GetTag("Line1.Speed")
|
||||
var md = (await svc.Hover(new HoverRequest("""return ctx.GetTag("Line1.Speed").Value;""", 1, 24))).Markdown;
|
||||
md.ShouldNotBeNull();
|
||||
md!.ShouldContain("Line1.Speed");
|
||||
md.ShouldContain("Double");
|
||||
md.ShouldContain("Tag");
|
||||
md.ShouldContain("MAIN-modbus");
|
||||
}
|
||||
|
||||
[Fact] public async Task Hover_on_unknown_tag_path_literal_reports_not_known()
|
||||
{
|
||||
var svc = new ScriptAnalysisService(new FakeCatalog());
|
||||
// caret on the path literal inside ctx.GetTag("Unknownz")
|
||||
var md = (await svc.Hover(new HoverRequest("""return ctx.GetTag("Unknownz").Value;""", 1, 22))).Markdown;
|
||||
md.ShouldNotBeNull();
|
||||
md!.ShouldContain("Not a known");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,4 +232,59 @@ public sealed class ScriptTagCatalogTests
|
||||
|
||||
paths.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>A SystemPlatform tag resolves by its "FolderPath.Name" dot-ref to a Tag-kind info with
|
||||
/// the configured DataType + DriverInstanceId.</summary>
|
||||
[Fact]
|
||||
public async Task GetTagInfo_systemplatform_tag_returns_tag_info()
|
||||
{
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
var info = await catalog.GetTagInfoAsync("DelmiaReceiver_001.DownloadPath", default);
|
||||
|
||||
info.ShouldNotBeNull();
|
||||
info!.Path.ShouldBe("DelmiaReceiver_001.DownloadPath");
|
||||
info.Kind.ShouldBe("Tag");
|
||||
info.DataType.ShouldBe("String");
|
||||
info.DriverInstanceId.ShouldBe("DRV-GALAXY");
|
||||
}
|
||||
|
||||
/// <summary>A virtual tag resolves by its leaf Name to a "Virtual tag"-kind info with no driver.</summary>
|
||||
[Fact]
|
||||
public async Task GetTagInfo_virtual_tag_returns_virtual_info_with_no_driver()
|
||||
{
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
var info = await catalog.GetTagInfoAsync("Computed", default);
|
||||
|
||||
info.ShouldNotBeNull();
|
||||
info!.Path.ShouldBe("Computed");
|
||||
info.Kind.ShouldBe("Virtual tag");
|
||||
info.DataType.ShouldBe("Double");
|
||||
info.DriverInstanceId.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>An unknown path resolves to null.</summary>
|
||||
[Fact]
|
||||
public async Task GetTagInfo_unknown_path_returns_null()
|
||||
{
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
(await catalog.GetTagInfoAsync("NoSuchPath", default)).ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>The lookup is case-SENSITIVE (Ordinal): a path that differs only in case does NOT
|
||||
/// resolve, mirroring the runtime DependencyMuxActor's StringComparer.Ordinal keying.</summary>
|
||||
[Fact]
|
||||
public async Task GetTagInfo_case_mismatch_returns_null_ordinal()
|
||||
{
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
(await catalog.GetTagInfoAsync("motor.speed", default)).ShouldBeNull();
|
||||
(await catalog.GetTagInfoAsync("Motor.Speed", default)).ShouldNotBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ public sealed class TagPathCompletionTests
|
||||
{
|
||||
public Task<IReadOnlyList<string>> GetPathsAsync(string? filter, CancellationToken ct)
|
||||
=> Task.FromResult<IReadOnlyList<string>>(new[] { "Motor.Speed", "Motor.Temp" });
|
||||
|
||||
public Task<ScriptTagInfo?> GetTagInfoAsync(string path, CancellationToken ct)
|
||||
=> Task.FromResult<ScriptTagInfo?>(null);
|
||||
}
|
||||
|
||||
private static readonly ScriptAnalysisService Svc = new(new FakeCatalog());
|
||||
|
||||
Reference in New Issue
Block a user