feat(adminui): writable checkbox in driver-typed tag editors (R2-11, 05/UNDER-6)

Models gain an optional bool? Writable (absent stays omitted; explicit false round-trips,
unknown keys preserved); the six editors gain a Writable checkbox (FOCAS disabled+read-only
hint). 9 model round-trip tests green. docker-dev /run verify of one editor deferred-live.
This commit is contained in:
Joseph Doherty
2026-07-13 11:22:12 -04:00
parent 4260b6ecaa
commit 4449baac69
14 changed files with 174 additions and 1 deletions
@@ -0,0 +1,88 @@
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
/// <summary>
/// R2-11 (05/UNDER-6): the new optional <c>writable</c> key round-trips through the driver-typed tag
/// config models — a value the editors' new checkbox authors. Absent stays absent (so existing configs
/// are untouched); an explicit <c>false</c> round-trips; unknown keys are preserved.
/// </summary>
public sealed class TagConfigModelWritableTests
{
[Fact]
public void Modbus_absent_writable_stays_omitted()
{
var m = ModbusTagConfigModel.FromJson("{\"region\":\"HoldingRegisters\",\"address\":1,\"dataType\":\"Int16\"}");
m.Writable.ShouldBeNull();
m.ToJson().ShouldNotContain("writable");
}
[Fact]
public void Modbus_explicit_false_round_trips()
{
var m = ModbusTagConfigModel.FromJson("{\"address\":1,\"writable\":false}");
m.Writable.ShouldBe(false);
var json = ModbusTagConfigModel.FromJson(m.ToJson());
json.Writable.ShouldBe(false);
}
[Fact]
public void Modbus_setting_writable_true_writes_the_key()
{
var m = ModbusTagConfigModel.FromJson("{\"address\":1}");
m.Writable = true;
ModbusTagConfigModel.FromJson(m.ToJson()).Writable.ShouldBe(true);
}
[Fact]
public void Writable_preserves_unknown_keys()
{
var m = ModbusTagConfigModel.FromJson("{\"address\":1,\"writable\":false,\"foo\":\"bar\"}");
m.ToJson().ShouldContain("foo");
m.ToJson().ShouldContain("bar");
}
[Fact]
public void S7_writable_round_trips()
{
var m = S7TagConfigModel.FromJson("{\"address\":\"DB1.DBW0\",\"writable\":false}");
m.Writable.ShouldBe(false);
S7TagConfigModel.FromJson(m.ToJson()).Writable.ShouldBe(false);
}
[Fact]
public void AbCip_writable_round_trips()
{
var m = AbCipTagConfigModel.FromJson("{\"tagPath\":\"Motor.Speed\",\"writable\":false}");
m.Writable.ShouldBe(false);
AbCipTagConfigModel.FromJson(m.ToJson()).Writable.ShouldBe(false);
}
[Fact]
public void AbLegacy_writable_round_trips()
{
var m = AbLegacyTagConfigModel.FromJson("{\"address\":\"N7:0\",\"writable\":false}");
m.Writable.ShouldBe(false);
AbLegacyTagConfigModel.FromJson(m.ToJson()).Writable.ShouldBe(false);
}
[Fact]
public void TwinCAT_writable_round_trips()
{
var m = TwinCATTagConfigModel.FromJson("{\"symbolPath\":\"MAIN.x\",\"writable\":false}");
m.Writable.ShouldBe(false);
TwinCATTagConfigModel.FromJson(m.ToJson()).Writable.ShouldBe(false);
}
[Fact]
public void Absent_writable_omitted_across_models()
{
ModbusTagConfigModel.FromJson("{\"address\":1}").ToJson().ShouldNotContain("writable");
S7TagConfigModel.FromJson("{\"address\":\"DB1.DBW0\"}").ToJson().ShouldNotContain("writable");
AbCipTagConfigModel.FromJson("{\"tagPath\":\"M.S\"}").ToJson().ShouldNotContain("writable");
AbLegacyTagConfigModel.FromJson("{\"address\":\"N7:0\"}").ToJson().ShouldNotContain("writable");
TwinCATTagConfigModel.FromJson("{\"symbolPath\":\"MAIN.x\"}").ToJson().ShouldNotContain("writable");
}
}