From 57c8973b0db6e318b3add537300f3ca98d4d5ac4 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 20 Aug 2026 08:42:56 -0400 Subject: [PATCH] =?UTF-8?q?fix(centralui)+test(centralui):=20Low-wave=20in?= =?UTF-8?q?tegration=20=E2=80=94=20T12=20Topology=20race=20fixture=20gains?= =?UTF-8?q?=20T13's=20IStaleInstanceProbe=20dependency;=20T11=20sandbox-ru?= =?UTF-8?q?n=20continuation=20skips=20StateHasChanged=20after=20Dispose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cross-task regression caught by the combined sweep: TopologyLiveUpdatesTimerDisposeRaceTests (T12) builds a real DeploymentService, whose constructor grew an IStaleInstanceProbe parameter in T13 (P7-6 up-to-date pre-filter) — 4 DI-resolution failures on the integration branch that neither task saw in isolation. Registered a substitute, matching TopologyPageTests. Bundle review Minor: ApiMethodForm/SharedScriptForm copied TemplateScriptDialog's sandbox pattern exactly, whose finally re-renders unconditionally; the plan asked the continuation to be guarded on disposal, so a _disposed flag now suppresses the post-cancel StateHasChanged (the reference dialog keeps its original shape — out of T11's scope). --- .../Components/Pages/Design/ApiMethodForm.razor | 5 ++++- .../Components/Pages/Design/SharedScriptForm.razor | 5 ++++- .../TopologyLiveUpdatesTimerDisposeRaceTests.cs | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/ApiMethodForm.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/ApiMethodForm.razor index 1779fa92..4cbe605b 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/ApiMethodForm.razor +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/ApiMethodForm.razor @@ -232,6 +232,7 @@ private Dictionary _paramValues = new(); private ScriptAnalysis.SandboxRunResult? _runResult; private CancellationTokenSource? _runCts; + private bool _disposed; protected override async Task OnInitializedAsync() { @@ -381,6 +382,7 @@ /// Cancels an in-flight Test Run when the component is torn down. public void Dispose() { + _disposed = true; _runCts?.Cancel(); _runCts?.Dispose(); _runCts = null; @@ -422,7 +424,8 @@ finally { _running = false; - StateHasChanged(); + // The run may have been cancelled BY Dispose — the component is gone, don't re-render it. + if (!_disposed) StateHasChanged(); } } diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/SharedScriptForm.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/SharedScriptForm.razor index 7c47ea2c..0c4826cf 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/SharedScriptForm.razor +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/SharedScriptForm.razor @@ -202,6 +202,7 @@ private Dictionary _paramValues = new(); private ScriptAnalysis.SandboxRunResult? _runResult; private CancellationTokenSource? _runCts; + private bool _disposed; // CentralUI-024: delegates to the shared helper so the claim type stays // resolved through JwtTokenService rather than a duplicated magic string. @@ -245,6 +246,7 @@ /// Cancels an in-flight Test Run when the component is torn down. public void Dispose() { + _disposed = true; _runCts?.Cancel(); _runCts?.Dispose(); _runCts = null; @@ -344,7 +346,8 @@ finally { _running = false; - StateHasChanged(); + // The run may have been cancelled BY Dispose — the component is gone, don't re-render it. + if (!_disposed) StateHasChanged(); } } diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TopologyLiveUpdatesTimerDisposeRaceTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TopologyLiveUpdatesTimerDisposeRaceTests.cs index 57d871f7..0344c9cd 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TopologyLiveUpdatesTimerDisposeRaceTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/TopologyLiveUpdatesTimerDisposeRaceTests.cs @@ -56,6 +56,8 @@ public class TopologyLiveUpdatesTimerDisposeRaceTests : BunitContext Services.AddSingleton(_siteRepo); Services.AddSingleton(_deployRepo); Services.AddSingleton(Substitute.For()); + // DeploymentService takes IStaleInstanceProbe since the Low-wave P7-6 pre-filter (T13). + Services.AddSingleton(Substitute.For()); Services.AddSingleton(_audit); var comms = new CommunicationService(