diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/TopologyAreaTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/TopologyAreaTests.cs
index 9f0d5119..b7d36afb 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/TopologyAreaTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/TopologyAreaTests.cs
@@ -6,8 +6,11 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment;
///
/// E2E coverage for the Topology page's area authoring surface — creating an area
-/// from the toolbar "+ Area" dialog (the site-picker variant), and the inline
-/// double-click rename on an area node's label.
+/// from the toolbar "+ Area" dialog (the site-picker variant), the inline
+/// double-click rename on an area node's label, the right-click "Move to Area…"
+/// flows for both an area (MoveAreaDialog) and an instance
+/// (MoveInstanceDialog), and the read-only "Diff" comparison
+/// (DiffDialog) for a deployed instance.
///
///
/// Every fact opens the page through , which
@@ -41,6 +44,26 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment;
/// ). A CLI read-back after each action
/// confirms the change actually persisted, not just that the toast appeared.
///
+///
+///
+/// Move flows: both "Move to Area…" context items open a one-<select>
+/// modal whose options carry the area name as their text (root-level areas render
+/// without indentation, so an exact visible-text match resolves). Unlike a text
+/// input, an HTML <select> fires onchange immediately on
+/// selection, so
+/// commits the @bind directly (no DispatchEventAsync dance). The
+/// area dialog title is "Move area '…' to…" (h6) and the instance dialog
+/// "Move '…' to…" (h6); both succeed with a single "moved" toast.
+///
+///
+///
+/// Diff: the instance "Diff" item is disabled for a NotDeployed
+/// instance, so the Diff fact deploys first (over the CLI) to enable it. The
+/// resulting DiffDialog is computed centrally (no site relay), so the
+/// comparison is deterministic for a deployed instance; its title is
+/// "Deployment Diff — <uniqueName>" (h5, em-dash) and it closes via
+/// the footer Close button.
+///
///
[Collection("Playwright")]
public class TopologyAreaTests : IClassFixture
@@ -157,6 +180,113 @@ public class TopologyAreaTests : IClassFixture
}
}
+ [SkippableFact]
+ public async Task MoveArea_UnderAnotherArea_ShowsMovedToast()
+ {
+ Skip.IfNot(_cluster.Available, ClusterAvailability.SkipReason);
+
+ var parentName = CliRunner.UniqueName("mvpar");
+ var childName = CliRunner.UniqueName("mvchild");
+ var parentId = await CliRunner.CreateAreaAsync(_cluster.SiteAId, parentName);
+ var childId = await CliRunner.CreateAreaAsync(_cluster.SiteAId, childName);
+ try
+ {
+ var page = await OpenStableTopologyAsync(_pw);
+
+ var childRow = page.Locator("div.tv-row", new() { HasText = childName });
+ await Assertions.Expect(childRow).ToBeVisibleAsync(new() { Timeout = 10_000 });
+ await childRow.ScrollIntoViewIfNeededAsync();
+ await childRow.ClickAsync(new() { Button = MouseButton.Right });
+ await page.Locator(".dropdown-menu.show button.dropdown-item:has-text('Move to Area')").ClickAsync();
+
+ // MoveAreaDialog title is "Move area '' to…" (h6) — distinct from the
+ // instance dialog ("Move '' to…"), so scope by the "Move area" text.
+ var dialog = page.Locator(".modal.show:has(h6.modal-title:has-text('Move area'))");
+ await Assertions.Expect(dialog).ToBeVisibleAsync();
+ // Root-level area options render without indentation, so the parent's name is
+ // the option's exact visible text. A