Task #147 — wire ModbusOptionsEditor into DriversTab
Branches the DriversTab driver-add form on driver type: - For DriverType=Modbus, render the typed <ModbusOptionsEditor> component shipped in #145 instead of the generic JSON textarea. - For other driver types, the existing textarea stays (other drivers ship their own typed editors per decision #94). On Save, when type is Modbus, the form serialises ModbusOptionsViewModel into the JSON DTO shape ModbusDriverFactoryExtensions consumes (host / port / unitId / family / keepAlive / reconnect / max*** / writeOnChangeOnly / etc.). Other types still pass the textarea contents verbatim. Drive-by fix: the DriverType dropdown listed "ModbusTcp" but the actual factory-registered name is "Modbus" — DriverInstanceBootstrapper would silently skip a row created with the old label because the factory lookup would miss. Renamed to match. Tests (2 new in ModbusOptionsViewModelTests): - DriversTab_Serialized_Defaults_RoundTrip_Through_Factory — unedited view-model serializes to a JSON the factory accepts; resulting ModbusDriverOptions matches the form defaults bit-for-bit. - DriversTab_Serializes_Edited_Values_Correctly — flipping Host / Port / UnitId / Family / MaxReadGap / WriteOnChangeOnly in the view model surfaces in the constructed driver's options. The serializer in the test mirrors DriversTab.razor's SerializeModbusOptions helper. If the form's serialization shape drifts, both must be updated together; that's the cost of testing through the JSON DTO without bUnit. Follow-up still open: the per-tag editor (ModbusAddressEditor wiring into EquipmentTab.razor + the bulk-import help-text update) — that's a separate surface that touches the equipment-row CRUD flow; covered as a follow-up when the equipment tag editor surface is next touched.
This commit is contained in:
@@ -14,6 +14,96 @@ namespace ZB.MOM.WW.OtOpcUa.Admin.Tests;
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class ModbusOptionsViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void DriversTab_Serialized_Defaults_RoundTrip_Through_Factory()
|
||||
{
|
||||
// #147 — the form's SaveAsync serialises ModbusOptionsViewModel to the JSON DTO
|
||||
// shape ModbusDriverFactoryExtensions consumes. This test pins the round-trip:
|
||||
// unedited form → JSON → driver instance → options match defaults.
|
||||
var vm = new ModbusOptionsEditor.ModbusOptionsViewModel();
|
||||
var json = SerializeForRoundTrip(vm);
|
||||
|
||||
var driver = ModbusDriverFactoryExtensions.CreateInstance("modbus-roundtrip", json);
|
||||
var opts = (ModbusDriverOptions)typeof(ModbusDriver)
|
||||
.GetField("_options", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!
|
||||
.GetValue(driver)!;
|
||||
|
||||
opts.Host.ShouldBe(vm.Host);
|
||||
opts.Port.ShouldBe(vm.Port);
|
||||
opts.UnitId.ShouldBe(vm.UnitId);
|
||||
opts.Family.ShouldBe(vm.Family);
|
||||
opts.MelsecSubFamily.ShouldBe(vm.MelsecSubFamily);
|
||||
opts.KeepAlive.Enabled.ShouldBe(vm.KeepAliveEnabled);
|
||||
opts.MaxRegistersPerRead.ShouldBe((ushort)vm.MaxRegistersPerRead);
|
||||
opts.MaxCoilsPerRead.ShouldBe((ushort)vm.MaxCoilsPerRead);
|
||||
opts.MaxReadGap.ShouldBe((ushort)vm.MaxReadGap);
|
||||
opts.WriteOnChangeOnly.ShouldBe(vm.WriteOnChangeOnly);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriversTab_Serializes_Edited_Values_Correctly()
|
||||
{
|
||||
// Sanity: changing a few fields in the view model produces a JSON the factory
|
||||
// accepts and the resulting driver carries the edited values.
|
||||
var vm = new ModbusOptionsEditor.ModbusOptionsViewModel
|
||||
{
|
||||
Host = "10.5.5.5",
|
||||
Port = 1502,
|
||||
UnitId = 7,
|
||||
Family = ModbusFamily.DL205,
|
||||
MaxReadGap = 12,
|
||||
WriteOnChangeOnly = true,
|
||||
};
|
||||
var json = SerializeForRoundTrip(vm);
|
||||
var driver = ModbusDriverFactoryExtensions.CreateInstance("modbus-edited", json);
|
||||
var opts = (ModbusDriverOptions)typeof(ModbusDriver)
|
||||
.GetField("_options", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!
|
||||
.GetValue(driver)!;
|
||||
|
||||
opts.Host.ShouldBe("10.5.5.5");
|
||||
opts.Port.ShouldBe(1502);
|
||||
opts.UnitId.ShouldBe((byte)7);
|
||||
opts.Family.ShouldBe(ModbusFamily.DL205);
|
||||
opts.MaxReadGap.ShouldBe((ushort)12);
|
||||
opts.WriteOnChangeOnly.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mirror of DriversTab.razor's SerializeModbusOptions — kept here so the test
|
||||
/// doesn't have to reach through Blazor component plumbing to invoke it. If the
|
||||
/// component method signature drifts, update both.
|
||||
/// </summary>
|
||||
private static string SerializeForRoundTrip(ModbusOptionsEditor.ModbusOptionsViewModel m) =>
|
||||
System.Text.Json.JsonSerializer.Serialize(new
|
||||
{
|
||||
host = m.Host,
|
||||
port = m.Port,
|
||||
unitId = m.UnitId,
|
||||
family = m.Family.ToString(),
|
||||
melsecSubFamily = m.MelsecSubFamily.ToString(),
|
||||
keepAlive = new
|
||||
{
|
||||
enabled = m.KeepAliveEnabled,
|
||||
timeMs = m.KeepAliveTimeSec * 1000,
|
||||
intervalMs = m.KeepAliveIntervalSec * 1000,
|
||||
retryCount = m.KeepAliveRetryCount,
|
||||
},
|
||||
reconnect = new
|
||||
{
|
||||
initialDelayMs = m.ReconnectInitialDelayMs,
|
||||
maxDelayMs = m.ReconnectMaxDelayMs,
|
||||
backoffMultiplier = m.ReconnectBackoffMultiplier,
|
||||
},
|
||||
maxRegistersPerRead = m.MaxRegistersPerRead,
|
||||
maxRegistersPerWrite = m.MaxRegistersPerWrite,
|
||||
maxCoilsPerRead = m.MaxCoilsPerRead,
|
||||
maxReadGap = m.MaxReadGap,
|
||||
useFC15ForSingleCoilWrites = m.UseFC15ForSingleCoilWrites,
|
||||
useFC16ForSingleRegisterWrites = m.UseFC16ForSingleRegisterWrites,
|
||||
writeOnChangeOnly = m.WriteOnChangeOnly,
|
||||
tags = Array.Empty<object>(),
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public void Defaults_Match_DriverOption_Defaults()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user