diff --git a/IoListTestingWindow.P1FirstPaint.cs b/IoListTestingWindow.P1FirstPaint.cs new file mode 100644 index 000000000..7cfa58263 --- /dev/null +++ b/IoListTestingWindow.P1FirstPaint.cs @@ -0,0 +1,40 @@ +using System.Windows; + +namespace ArIED61850Tester; + +/// +/// P1 first-paint guard for the FAT workspace. +/// +/// The golden/P0 workspace still declares legacy relay-time columns in XAML and replaces +/// them with the V2 schema from the ContentRendered path. Installing the existing V2 +/// workspace during Loaded keeps the visual tree available while ensuring the first +/// visible FAT paint already uses LIVE VALUE / VALUE 1 / VALUE 2. +/// +/// InstallFatV2WorkspaceUx is intentionally reused and remains idempotent. No runtime +/// virtualization, protocol acquisition, command, evidence, or ARIEC61850 behavior is +/// changed here. +/// +public partial class IoListTestingWindow +{ + private static readonly bool P1FirstPaintRegistered = RegisterP1FirstPaint(); + + private static bool RegisterP1FirstPaint() + { + EventManager.RegisterClassHandler( + typeof(IoListTestingWindow), + FrameworkElement.LoadedEvent, + new RoutedEventHandler(P1FirstPaint_Loaded)); + return true; + } + + private static void P1FirstPaint_Loaded(object sender, RoutedEventArgs e) + { + if (sender is not IoListTestingWindow window || + !ReferenceEquals(e.OriginalSource, window)) + { + return; + } + + window.InstallFatV2WorkspaceUx(); + } +} diff --git a/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs b/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs new file mode 100644 index 000000000..cb65b82df --- /dev/null +++ b/tests/ARSAS.Tests/IoFatP1FirstPaintRegressionTests.cs @@ -0,0 +1,61 @@ +namespace ARSAS.Tests; + +public sealed class IoFatP1FirstPaintRegressionTests +{ + [Fact] + public void P1_InstallsExistingFatV2SchemaOnLoadedBeforeFirstVisibleRender() + { + var source = File.ReadAllText(FindRepoFile("IoListTestingWindow.P1FirstPaint.cs")); + var handlerStart = source.IndexOf("private static void P1FirstPaint_Loaded", StringComparison.Ordinal); + Assert.True(handlerStart >= 0); + var handler = source[handlerStart..]; + + Assert.Contains("FrameworkElement.LoadedEvent", source, StringComparison.Ordinal); + Assert.Contains("window.InstallFatV2WorkspaceUx();", handler, StringComparison.Ordinal); + Assert.Contains("ReferenceEquals(e.OriginalSource, window)", handler, StringComparison.Ordinal); + Assert.DoesNotContain("ContentRendered", handler, StringComparison.Ordinal); + Assert.DoesNotContain("Dispatcher.BeginInvoke", handler, StringComparison.Ordinal); + } + + [Fact] + public void P1_V2InstallerIsIdempotentAndDefinesOnlyApprovedEvidenceColumns() + { + var source = File.ReadAllText(FindRepoFile("IoListTestingWindow.FatV2Ux.cs")); + + Assert.Contains("if (!_fatV2UxInstalled)", source, StringComparison.Ordinal); + Assert.Contains("_fatV2UxInstalled = true;", source, StringComparison.Ordinal); + Assert.Contains("grid.Columns.Clear();", source, StringComparison.Ordinal); + Assert.Contains("Header = \"LIVE VALUE\"", source, StringComparison.Ordinal); + Assert.Contains("FatValueSlot.Value1", source, StringComparison.Ordinal); + Assert.Contains("FatValueSlot.Value2", source, StringComparison.Ordinal); + Assert.DoesNotContain("\"ON RELAY TIME\"", source, StringComparison.Ordinal); + Assert.DoesNotContain("\"OFF RELAY TIME\"", source, StringComparison.Ordinal); + } + + [Fact] + public void P1_FirstPaintGuardDoesNotTouchVirtualizationProtocolOrEvidence() + { + var source = File.ReadAllText(FindRepoFile("IoListTestingWindow.P1FirstPaint.cs")); + + Assert.DoesNotContain("SetVirtualizationMode", source, StringComparison.Ordinal); + Assert.DoesNotContain("SetIsVirtualizing", source, StringComparison.Ordinal); + Assert.DoesNotContain("ReadObject", source, StringComparison.Ordinal); + Assert.DoesNotContain("ReadAsync", source, StringComparison.Ordinal); + Assert.DoesNotContain("CaptureCurrentEvidence", source, StringComparison.Ordinal); + } + + private static string FindRepoFile(string relativePath) + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, relativePath); + if (File.Exists(candidate)) + return candidate; + directory = directory.Parent; + } + + throw new FileNotFoundException( + $"Could not locate repository file '{relativePath}' from '{AppContext.BaseDirectory}'."); + } +}