feat(uns): TagConfig JSON helper + editor map + TagModal dispatch scaffold (F-uns-1 T2)

This commit is contained in:
Joseph Doherty
2026-06-09 09:22:12 -04:00
parent d9dbd7917a
commit fd9fa75d0e
4 changed files with 223 additions and 5 deletions
@@ -0,0 +1,134 @@
using System.Text.Json.Nodes;
using Shouldly;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
public sealed class TagConfigJsonTests
{
private enum Flavor { None, Vanilla, Chocolate }
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("{not json")]
[InlineData("[1,2]")]
public void ParseOrNew_returns_empty_object_for_unusable_input(string? json)
{
var obj = TagConfigJson.ParseOrNew(json);
obj.ShouldNotBeNull();
obj.Count.ShouldBe(0);
}
[Fact]
public void ParseOrNew_parses_an_object()
{
var obj = TagConfigJson.ParseOrNew("""{"foo":"bar"}""");
obj.Count.ShouldBe(1);
TagConfigJson.GetString(obj, "foo").ShouldBe("bar");
}
[Fact]
public void RoundTrip_preserves_unknown_keys()
{
var obj = TagConfigJson.ParseOrNew("""{"foo":"bar","region":"X"}""");
TagConfigJson.Set(obj, "region", "Y");
var json = TagConfigJson.Serialize(obj);
json.ShouldContain("\"foo\":\"bar\"");
json.ShouldContain("\"region\":\"Y\"");
}
[Fact]
public void GetString_returns_null_when_absent()
{
var obj = new JsonObject();
TagConfigJson.GetString(obj, "missing").ShouldBeNull();
}
[Fact]
public void GetInt_reads_an_int()
{
var obj = TagConfigJson.ParseOrNew("""{"register":40001}""");
TagConfigJson.GetInt(obj, "register").ShouldBe(40001);
}
[Fact]
public void GetInt_falls_back_when_absent()
{
var obj = new JsonObject();
TagConfigJson.GetInt(obj, "register", fallback: 7).ShouldBe(7);
}
[Fact]
public void GetInt_falls_back_when_value_is_a_non_scalar_node()
{
// A nested object/array under the key must not throw — GetInt returns the fallback.
var obj = TagConfigJson.ParseOrNew("""{"register":{},"other":[1,2]}""");
TagConfigJson.GetInt(obj, "register", fallback: 7).ShouldBe(7);
TagConfigJson.GetInt(obj, "other", fallback: 9).ShouldBe(9);
}
[Fact]
public void GetString_returns_null_when_value_is_a_non_string_node()
{
var obj = TagConfigJson.ParseOrNew("""{"register":40001,"nested":{}}""");
TagConfigJson.GetString(obj, "register").ShouldBeNull();
TagConfigJson.GetString(obj, "nested").ShouldBeNull();
}
[Fact]
public void GetEnum_parses_by_name_case_insensitive()
{
var obj = TagConfigJson.ParseOrNew("""{"flavor":"chocolate"}""");
TagConfigJson.GetEnum(obj, "flavor", Flavor.None).ShouldBe(Flavor.Chocolate);
}
[Fact]
public void GetEnum_falls_back_when_absent()
{
var obj = new JsonObject();
TagConfigJson.GetEnum(obj, "flavor", Flavor.Vanilla).ShouldBe(Flavor.Vanilla);
}
[Fact]
public void GetEnum_falls_back_on_garbage()
{
var obj = TagConfigJson.ParseOrNew("""{"flavor":"strawberry"}""");
TagConfigJson.GetEnum(obj, "flavor", Flavor.Vanilla).ShouldBe(Flavor.Vanilla);
}
[Fact]
public void Set_writes_an_enum_as_its_name_string()
{
var obj = new JsonObject();
TagConfigJson.Set(obj, "flavor", Flavor.Chocolate);
var json = TagConfigJson.Serialize(obj);
json.ShouldContain($"\"flavor\":\"{Flavor.Chocolate}\"");
}
[Fact]
public void Set_with_null_removes_the_key()
{
var obj = TagConfigJson.ParseOrNew("""{"region":"X"}""");
TagConfigJson.Set(obj, "region", null);
TagConfigJson.GetString(obj, "region").ShouldBeNull();
}
}