using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Deployment;
///
/// MV-14: the Instance Configure attribute-override panel uses the shared
/// AttributeListEditor for a List attribute (whole-list replacement; the
/// element type is fixed by the base attribute, so the type select is hidden via
/// ShowElementType="false"). Loading an existing override decodes its JSON
/// into rows; saving encodes the rows back to canonical JSON with a pre-submit
/// round-trip guard; clearing removes the override row. InstanceConfigure
/// is a heavyweight page (multiple injected services incl. InstanceService
/// and the flattening pipeline), so — consistent with the native-alarm and
/// template-editor coverage — these are structural assertions over the component
/// source that pin the wiring, plus a real codec round-trip mirroring what the
/// page does on load/save.
///
public class InstanceConfigureListOverrideTests
{
private static string InstanceConfigureMarkup
{
get
{
var dir = AppContext.BaseDirectory;
for (var i = 0; i < 6 && dir is not null; i++)
dir = Directory.GetParent(dir)?.FullName;
return File.ReadAllText(Path.Combine(dir!, "src", "ZB.MOM.WW.ScadaBridge.CentralUI",
"Components", "Pages", "Deployment", "InstanceConfigure.razor"));
}
}
[Fact]
public void ListOverride_RevealsSharedEditor_WithElementTypeHidden()
{
var markup = InstanceConfigureMarkup;
// Conditional reveal on a List attribute.
Assert.Contains("attr.DataType == DataType.List", markup);
Assert.Contains(" OnListRowsChanged(attr.Name, r))\"", markup);
}
[Fact]
public void ListOverride_DecodesOnLoad_AndEncodesOnSaveWithGuard()
{
var markup = InstanceConfigureMarkup;
// Load: effective value (existing override JSON or template default)
// decoded into rows via the shared codec, malformed → empty rows.
Assert.Contains("DecodeListRows(", markup);
Assert.Contains("catch (FormatException)", markup);
// Save: rows encoded to canonical JSON + round-trip Decode guard.
Assert.Contains("AttributeValueCodec.Encode(GetListRows(", markup);
Assert.Contains("AttributeValueCodec.Decode(json, DataType.List, elementType)", markup);
Assert.Contains("_overrideErrors", markup);
}
[Fact]
public void ListOverride_ClearRemovesTheOverrideRow()
{
var markup = InstanceConfigureMarkup;
Assert.Contains("ClearListOverride", markup);
// Repository-direct delete (the page only edits InstanceConfigure; no new
// server method) — same pattern as native-alarm-source overrides.
Assert.Contains("DeleteInstanceAttributeOverrideAsync", markup);
Assert.Contains("HasOverrideRow", markup);
}
[Fact]
public void NonListOverride_KeepsSingleInputUx()
{
var markup = InstanceConfigureMarkup;
// The scalar path still binds the single text input via the existing helpers.
Assert.Contains("GetOverrideValue(attr.Name)", markup);
Assert.Contains("OnOverrideChanged(attr.Name, e)", markup);
}
[Fact]
public void EncodedRows_RoundTripThroughCodec_AsThePageDoes()
{
// Mirrors the load (Decode → rows) / save (Encode → JSON) cycle the page runs.
var json = AttributeValueCodec.Encode(new List { "10", "20", "30" });
var decoded = AttributeValueCodec.Decode(json, DataType.List, DataType.Int32);
var list = Assert.IsType>(decoded);
Assert.Equal(new[] { 10, 20, 30 }, list);
// The re-encoded form is stable, so a clean override round-trips losslessly.
var roundTrip = AttributeValueCodec.Encode(decoded);
Assert.Equal(json, roundTrip);
}
[Fact]
public void MalformedListElement_SurfacesFormatException_ForInlineError()
{
// The pre-submit guard catches this and shows it inline rather than crashing.
var json = AttributeValueCodec.Encode(new List { "1", "not-a-number" });
Assert.Throws(
() => AttributeValueCodec.Decode(json, DataType.List, DataType.Int32));
}
}