feat(mtconnect): typed AdminUI tag-config editor + browse round-trip (Task 18 Part A)
Registers the MTConnect typed tag editor in TagConfigEditorMap /
TagConfigValidator (keyed off DriverTypeNames.MTConnect, never a literal) and
adds the razor shell over Task 17's MTConnectTagConfigModel.
The shell stays trivial: the /probe-sourced mtCategory/mtType/mtSubType/units
row is rendered disabled + readonly with NO binding of any kind, and no
inferred-type hint is shown — MTConnectDataTypeInference.Infer() also keys on
the DataItem `representation`, which the tag-config model does not carry, so
calling it here would agree with the browse picker for ordinary items and
silently disagree for exactly the DATA_SET/TABLE/TIME_SERIES ones.
Also fixes the picker -> editor round trip, which was broken in both
directions:
- RawBrowseCommitMapper.BuildTagConfig had no MTConnect branch, so a committed
tag landed as the generic {"address": "<dataItemId>"} while the model read
only `fullName`. The data plane kept working (the driver accepts all three id
spellings), so the only symptom was an empty DataItem-id field in the editor
— and saving from that state stamped fullName:"" over the tag.
- MTConnectTagConfigModel.FromJson now accepts dataItemId/address as fallbacks
(fixing tags committed before this change) and ToJson normalises onto
`fullName`, dropping the aliases so the two spellings cannot drift.
This commit is contained in:
+102
@@ -0,0 +1,102 @@
|
||||
using System.Text.Json.Nodes;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
|
||||
|
||||
/// <summary>
|
||||
/// Pins the browse-picker → typed-editor round trip for MTConnect, the driver's <b>primary</b>
|
||||
/// authoring path (a tag is committed from the universal browser, then confirmed/overridden in the
|
||||
/// typed editor).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The defect this guards: <see cref="RawBrowseCommitMapper.BuildTagConfig"/> had no MTConnect
|
||||
/// branch, so a committed tag landed as the generic <c>{"address": "<dataItemId>"}</c>
|
||||
/// while <see cref="MTConnectTagConfigModel"/> read only <c>fullName</c>. The data plane kept
|
||||
/// working (the driver accepts all three id spellings), so the ONLY symptom was an empty
|
||||
/// DataItem-id field in the editor — and saving from that state stamped <c>fullName: ""</c>
|
||||
/// over the tag. Razor is exactly what this repo cannot unit-test, so both halves of the fix
|
||||
/// are pinned here in plain C#.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Both halves are asserted: the mapper now writes the canonical <c>fullName</c> (fixes new
|
||||
/// commits), and the model accepts <c>dataItemId</c>/<c>address</c> as fallbacks (fixes tags
|
||||
/// already committed before the fix).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class MTConnectBrowseCommitRoundTripTests
|
||||
{
|
||||
[Fact]
|
||||
public void BuildTagConfig_writes_the_canonical_fullName_key_for_MTConnect()
|
||||
{
|
||||
var json = RawBrowseCommitMapper.BuildTagConfig(DriverTypeNames.MTConnect, "avail");
|
||||
|
||||
var o = JsonNode.Parse(json)!.AsObject();
|
||||
o["fullName"]!.GetValue<string>().ShouldBe("avail");
|
||||
o.ContainsKey("address").ShouldBeFalse(
|
||||
"the generic fallback key must not be used now that MTConnect has a typed editor");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_browse_committed_tag_round_trips_through_the_typed_editor_model()
|
||||
{
|
||||
var committed = RawBrowseCommitMapper.BuildTagConfig(DriverTypeNames.MTConnect, "Xact");
|
||||
|
||||
var model = MTConnectTagConfigModel.FromJson(committed);
|
||||
|
||||
model.FullName.ShouldBe("Xact");
|
||||
model.Validate().ShouldBeNull("a browse-committed tag must open in the editor already valid");
|
||||
MTConnectTagConfigModel.FromJson(model.ToJson()).FullName.ShouldBe("Xact");
|
||||
}
|
||||
|
||||
// ---- legacy blobs committed before the mapper fix -------------------------------------------
|
||||
|
||||
[Theory]
|
||||
[InlineData("address")]
|
||||
[InlineData("dataItemId")]
|
||||
public void FromJson_accepts_the_drivers_alternate_id_spellings(string key)
|
||||
{
|
||||
var model = MTConnectTagConfigModel.FromJson($$"""{"{{key}}":"Ypos"}""");
|
||||
|
||||
model.FullName.ShouldBe("Ypos");
|
||||
model.Validate().ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FullName_wins_over_dataItemId_which_wins_over_address()
|
||||
{
|
||||
MTConnectTagConfigModel
|
||||
.FromJson("""{"fullName":"a","dataItemId":"b","address":"c"}""")
|
||||
.FullName.ShouldBe("a");
|
||||
|
||||
MTConnectTagConfigModel
|
||||
.FromJson("""{"dataItemId":"b","address":"c"}""")
|
||||
.FullName.ShouldBe("b");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_blank_alias_does_not_shadow_a_populated_one()
|
||||
=> MTConnectTagConfigModel
|
||||
.FromJson("""{"fullName":" ","address":"c"}""")
|
||||
.FullName.ShouldBe("c");
|
||||
|
||||
[Fact]
|
||||
public void Saving_a_legacy_blob_normalises_it_onto_fullName_and_drops_the_aliases()
|
||||
{
|
||||
var model = MTConnectTagConfigModel.FromJson("""{"address":"Zpos","units":"MILLIMETER"}""");
|
||||
|
||||
var o = JsonNode.Parse(model.ToJson())!.AsObject();
|
||||
|
||||
o["fullName"]!.GetValue<string>().ShouldBe("Zpos");
|
||||
o.ContainsKey("address").ShouldBeFalse(
|
||||
"leaving a second id spelling behind lets the two drift apart on the next edit");
|
||||
o.ContainsKey("dataItemId").ShouldBeFalse();
|
||||
// Unrelated keys are still preserved — normalisation is not a purge.
|
||||
o["units"]!.GetValue<string>().ShouldBe("MILLIMETER");
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ public sealed class TagConfigDriverTypeNameGuardTests
|
||||
DriverTypeNames.FOCAS,
|
||||
DriverTypeNames.OpcUaClient,
|
||||
DriverTypeNames.Calculation,
|
||||
DriverTypeNames.MTConnect,
|
||||
];
|
||||
|
||||
[Theory]
|
||||
@@ -46,6 +47,7 @@ public sealed class TagConfigDriverTypeNameGuardTests
|
||||
DriverTypeNames.TwinCAT,
|
||||
DriverTypeNames.FOCAS,
|
||||
DriverTypeNames.OpcUaClient,
|
||||
DriverTypeNames.MTConnect,
|
||||
];
|
||||
|
||||
[Theory]
|
||||
|
||||
Reference in New Issue
Block a user