Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Design/TemplateNativeAlarmSourceEditorTests.cs
T
Joseph Doherty a506b19d17 refactor(centralui): migrate TemplateEdit's four page-embedded modals to the DialogService host (M10 residual)
TemplateEdit was the last page still hand-rolling its own modal chrome after
M10 — T34c only tokenized its backdrops. All four member-authoring forms
(Attribute, Alarm, Native Alarm Source, Script) now open through
IDialogService.ShowAsync, so the single DialogHost in MainLayout owns the
backdrop, focus trap, Escape and focus restoration.

Pattern copied from the already-migrated pages (MoveDataConnectionDialog +
Templates' Move/Rename dialogs): each body is its own component beside the page
taking a DialogContext<bool>, owning its form state, rendering validation and
server errors INLINE while staying open, and closing with Close(true) only once
the save succeeded — at which point the page reloads. An inline RenderFragment
would NOT have worked: DialogHost renders the captured fragment in its own tree,
so the page's StateHasChanged could never refresh it.

Persistence deliberately stayed on the page (it owns TemplateService, the
inherited-member rules, and the repository-direct native-source path) and is
reached through an OnSaveAsync delegate returning null on success or the message
to display. Behaviour preserved verbatim, including the List-attribute encode +
Decode round-trip check, the name/trigger-type read-only-on-edit rules, the
duplicate-native-source-name guard, and NormalizeExecutionTimeout.

TemplateScriptDialog keeps all four tab panels mounted (Monaco and the JSONJoy
island must not tear down on tab switch) and hosts the Test Run panel, which
needs the live unsaved editor buffer, so it injects ScriptAnalysisService
directly and cancels an in-flight run on dispose.

Extraction moved markup that three structural source-scanning tests pinned;
all three were repointed at the new files rather than weakened:
  - TemplateNativeAlarmSourceEditorTests (+ a new test asserting the form is
    host-mounted and the body renders no chrome of its own)
  - AttributeListEditorTests (list-editor reveal now in the dialog body)
  - TestRunWarningTests (Real I/O warning travelled with the script panel)

Build 0/0; CentralUI.Tests 973/973 green.
2026-08-01 11:28:20 -04:00

102 lines
4.7 KiB
C#

namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Design;
/// <summary>
/// Task 24: the template editor exposes a Native Alarm Sources subsection (a new
/// tab) for authoring read-only native alarm bindings. <c>TemplateEdit</c> is a
/// heavyweight page (~10 injected services with their own graphs); like the other
/// TemplateEdit coverage in this suite (see <c>TestRunWarningTests</c>), these are
/// structural assertions over the component source that pin the subsection's
/// wiring — the tab, the authoring form (connection dropdown, source reference,
/// filter, lock), and the repository-direct CRUD calls — so it cannot silently
/// regress. The CRUD behaviour itself is covered end-to-end by the ManagementActor
/// native-alarm-source handler tests.
/// </summary>
public class TemplateNativeAlarmSourceEditorTests
{
private static string DesignComponentsRoot
{
get
{
var dir = AppContext.BaseDirectory;
for (var i = 0; i < 6 && dir is not null; i++)
dir = Directory.GetParent(dir)?.FullName;
return Path.Combine(dir!, "src", "ZB.MOM.WW.ScadaBridge.CentralUI",
"Components", "Pages", "Design");
}
}
private static string TemplateEditMarkup
=> File.ReadAllText(Path.Combine(DesignComponentsRoot, "TemplateEdit.razor"));
// M10 residual: the authoring form moved out of the page into the dialog body
// component hosted by IDialogService.ShowAsync. The field-level assertions
// below follow it there; the tab, the connection filtering, and the CRUD
// wiring all still live on the page.
private static string NativeSourceDialogMarkup
=> File.ReadAllText(Path.Combine(DesignComponentsRoot, "TemplateNativeAlarmSourceDialog.razor"));
[Fact]
public void TemplateEditor_HasNativeAlarmsTab()
{
var markup = TemplateEditMarkup;
Assert.Contains("_activeTab = \"native-alarms\"", markup);
Assert.Contains("Native Alarms", markup);
Assert.Contains("RenderNativeAlarmsTab", markup);
Assert.Contains("Native Alarm Sources", markup);
}
[Fact]
public void NativeAlarmsForm_HasConnectionSourceFilterAndLockFields()
{
var pageMarkup = TemplateEditMarkup;
// Connection dropdown filtered to alarm-capable protocols via the
// single-source-of-truth Commons helper. The OpcUa/MxGateway literal set
// now lives in AlarmCapableProtocols (Commons) — pinned by
// AlarmCapableProtocolsTests — so this page only needs to delegate to it.
Assert.Contains("_alarmCapableConnections", pageMarkup);
Assert.Contains("AlarmCapableProtocols.IsAlarmCapable", pageMarkup);
// …and hands the filtered list to the dialog body.
Assert.Contains("AlarmCapableConnections=", pageMarkup);
// The authoring form fields now live in the dialog body component.
var dialogMarkup = NativeSourceDialogMarkup;
Assert.Contains("@bind=\"_nasName\"", dialogMarkup);
Assert.Contains("@bind=\"_nasConnection\"", dialogMarkup);
Assert.Contains("@bind=\"_nasSourceRef\"", dialogMarkup);
Assert.Contains("@bind=\"_nasFilter\"", dialogMarkup);
Assert.Contains("@bind=\"_nasIsLocked\"", dialogMarkup);
}
[Fact]
public void NativeAlarmsForm_IsHostedByTheDialogService()
{
// M10 residual: the page-embedded modal is gone — the form opens through
// IDialogService.ShowAsync so the host owns the backdrop, focus trap,
// Escape, and focus restoration.
var pageMarkup = TemplateEditMarkup;
Assert.Contains("<TemplateNativeAlarmSourceDialog", pageMarkup);
Assert.DoesNotContain("_showNativeSourceForm", pageMarkup);
// The body renders no chrome of its own (no backdrop / modal shell).
var dialogMarkup = NativeSourceDialogMarkup;
Assert.DoesNotContain("sb-modal-backdrop", dialogMarkup);
Assert.Contains("DialogContext<bool> Context", dialogMarkup);
}
[Fact]
public void NativeAlarmsCrud_WiresRepositoryAddUpdateDeleteWithSave()
{
var markup = TemplateEditMarkup;
Assert.Contains("AddTemplateNativeAlarmSourceAsync", markup);
Assert.Contains("UpdateTemplateNativeAlarmSourceAsync", markup);
Assert.Contains("DeleteTemplateNativeAlarmSourceAsync", markup);
Assert.Contains("GetNativeAlarmSourcesByTemplateIdAsync", markup);
Assert.Contains("SaveChangesAsync", markup);
// Add/edit/delete handlers are wired to the UI.
Assert.Contains("OpenAddNativeSourceDialog", markup);
Assert.Contains("OpenEditNativeSourceDialog", markup);
Assert.Contains("SaveNativeSourceDraftAsync", markup);
Assert.Contains("DeleteNativeSource", markup);
}
}