diff --git a/docs/reqstream/ots/appium.yaml b/docs/reqstream/ots/appium.yaml index 4e4bc43..6855e13 100644 --- a/docs/reqstream/ots/appium.yaml +++ b/docs/reqstream/ots/appium.yaml @@ -10,6 +10,9 @@ sections: A system-level UI automation tier is needed that exercises the actual compiled application in a real window, complementing the headless and unit test tiers, and Windows is the only platform with a provisioned CI Appium driver today. tests: - 'DesktopApp_Launch_ShowsMainWindowWithExpectedTitle' + - 'DesktopApp_ViewMenu_WorkspacePanelMenuItem_TogglesWorkspacePanel' + - 'DesktopApp_ViewMenu_PredefinedViewsMenuItem_TogglesPredefinedViewsPanel' + - 'DesktopApp_ViewMenu_DiagnosticsMenuItem_TogglesDiagnosticsPanel' - id: 'Appium-LocateControlsByAutomationId' title: 'The SysML2Workbench repository shall expose interactive controls to Appium via AutomationProperties.AutomationId for reliable, name-independent lookup.' diff --git a/docs/verification/ots/appium.md b/docs/verification/ots/appium.md index 30d6379..3f3e4a6 100644 --- a/docs/verification/ots/appium.md +++ b/docs/verification/ots/appium.md @@ -64,6 +64,31 @@ dialog's `AboutDialogOkButton` becomes visible, then dismisses it - proving a full menu-click-to-modal-dialog round trip works end-to-end through the real windowed application. +**DesktopApp_ViewMenu_WorkspacePanelMenuItem_TogglesWorkspacePanel**, +**DesktopApp_ViewMenu_PredefinedViewsMenuItem_TogglesPredefinedViewsPanel**, and +**DesktopApp_ViewMenu_DiagnosticsMenuItem_TogglesDiagnosticsPanel**: Unlike the +`IsDiscoverableAndEnabled` scenarios above, these drive a full close/reopen +round trip through `MainWindowView`'s `ShowOrFocusPanel`, which implements +checkbox-style toggle semantics: a panel that is open (`Tool.IsOpen`) is +destroyed/closed via `WorkbenchDockFactory.CloseDockable` on click, while a +closed panel is restored, activated, and focused on click. Each test clicks +its View-menu item once and polls for the corresponding panel's own +automation id (`WorkspaceAddFileButton`, `PredefinedViewsListBox`, +`DiagnosticsListBox`) to disappear - proving the panel was actually closed, +not merely that the menu item is clickable - then clicks it a second time +and polls for the same control to reappear and be displayed, proving +reopening also brings the panel into view. All three panels start open per +`WorkbenchDockFactory.CreateLayout`, so the first click always closes and +the second always reopens, and each test restores the panel to its +original open state by the end, leaving no residual effect for later +tests. Checked state is verified purely through panel content presence +rather than by reading the menu item's own checked state through UI +Automation, because Avalonia's Win32 automation bridge does not currently +expose the UIA Toggle pattern for `MenuItem` to native automation clients +(confirmed via `System.Windows.Automation` and Inspect.exe, independent of +this codebase) - a real Avalonia platform limitation, not a defect in this +test suite. + **DesktopApp_QueryDialog_AddTypeFilterButton_CapturesInspectionScreenshot**: Opens the Query dialog and captures a cropped PNG of the shared `ElementFilterView`'s "+" add-type-filter button (`AddTypeFilterButton`) to `artifacts/inspection/query-dialog-add-type-filter-button.png` via `InspectionScreenshot.CaptureElement`, then diff --git a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/DiagnosticsToolView.axaml b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/DiagnosticsToolView.axaml index f88038c..413905f 100644 --- a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/DiagnosticsToolView.axaml +++ b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/DiagnosticsToolView.axaml @@ -7,7 +7,8 @@ - + diff --git a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/MainWindowView.axaml b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/MainWindowView.axaml index c8e83e8..528d6e7 100644 --- a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/MainWindowView.axaml +++ b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/MainWindowView.axaml @@ -35,17 +35,17 @@ - + - + - + diff --git a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/MainWindowView.axaml.cs b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/MainWindowView.axaml.cs index 1fdb104..9288da8 100644 --- a/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/MainWindowView.axaml.cs +++ b/src/DemaConsulting.SysML2Workbench/AppShellSubsystem/MainWindowView.axaml.cs @@ -149,14 +149,23 @@ private async void OnOpenQueryDialogClick(object? sender, RoutedEventArgs e) } /// - /// Restores to its original dock if it was hidden by a prior close (via - /// 's HideToolsOnClose setting, a safe no-op if it is not - /// currently hidden), then makes it the active and focused dockable in its owning dock. This never hides - /// an already-open panel: clicking a View-menu item for a visible panel simply (re)focuses it. + /// Toggles 's open/closed state, matching its View-menu item's checkable + /// IsChecked binding (bound to ). If the tool is currently closed + /// (hidden by a prior close via 's HideToolsOnClose setting, or + /// never opened), this restores it to its original dock and makes it the active and focused dockable + /// there. If the tool is already open - regardless of whether it is the currently visible/front tab in a + /// shared (Workspace and Predefined Views share one) - this closes it instead, + /// so the menu item behaves as a plain open/close toggle rather than requiring it to already be focused. /// - /// The panel to show or bring into focus, reusing its existing long-lived instance. + /// The panel to show or hide, reusing its existing long-lived instance either way. private void ShowOrFocusPanel(Tool tool) { + if (tool.IsOpen) + { + _dockFactory.CloseDockable(tool); + return; + } + _dockFactory.RestoreDockable(tool); _dockFactory.SetActiveDockable(tool); diff --git a/test/DemaConsulting.SysML2Workbench.IntegrationTests/MainWindowShellIntegrationTests.cs b/test/DemaConsulting.SysML2Workbench.IntegrationTests/MainWindowShellIntegrationTests.cs index a5d402b..174b9a9 100644 --- a/test/DemaConsulting.SysML2Workbench.IntegrationTests/MainWindowShellIntegrationTests.cs +++ b/test/DemaConsulting.SysML2Workbench.IntegrationTests/MainWindowShellIntegrationTests.cs @@ -115,6 +115,106 @@ public void DesktopApp_ViewMenu_DiagnosticsMenuItem_IsDiscoverableAndEnabled() AssertMenuItemIsDiscoverableAndEnabled("View", "DiagnosticsMenuItem"); } + /// + /// Validates that clicking the View menu's "Workspace" item, found by the WorkspacePanelMenuItem + /// automation id, toggles the Workspace panel open/closed - proving MainWindowView's + /// ShowOrFocusPanel genuinely destroys/recreates the panel's dock presence (checkbox semantics: + /// checked means the panel exists and clicking removes it, unchecked means it does not and clicking + /// creates/shows it), and that opening it also brings it into focus. See + /// for the full round-trip this drives. + /// + [Fact] + public void DesktopApp_ViewMenu_WorkspacePanelMenuItem_TogglesWorkspacePanel() + { + AssertMenuItemTogglesPanel("View", "WorkspacePanelMenuItem", "WorkspaceAddFileButton"); + } + + /// + /// Validates that clicking the View menu's "Predefined Views" item, found by the + /// PredefinedViewsMenuItem automation id, toggles the Predefined Views panel open/closed - see + /// for the shared + /// checkbox-toggle semantics this proves. + /// + [Fact] + public void DesktopApp_ViewMenu_PredefinedViewsMenuItem_TogglesPredefinedViewsPanel() + { + AssertMenuItemTogglesPanel("View", "PredefinedViewsMenuItem", "PredefinedViewsListBox"); + } + + /// + /// Validates that clicking the View menu's "Diagnostics" item, found by the DiagnosticsMenuItem + /// automation id, toggles the Diagnostics panel open/closed - see + /// for the shared + /// checkbox-toggle semantics this proves. + /// + [Fact] + public void DesktopApp_ViewMenu_DiagnosticsMenuItem_TogglesDiagnosticsPanel() + { + AssertMenuItemTogglesPanel("View", "DiagnosticsMenuItem", "DiagnosticsListBox"); + } + + /// + /// Drives one full close/reopen round trip through a View-menu panel-toggle item, leaving the panel open + /// again afterward - the state every panel starts in per WorkbenchDockFactory.CreateLayout - so + /// later tests in this shared session see no residual side effect. Confirms + /// the toggle behavior purely via the panel's own control () + /// becoming absent then present again, rather than reading the menu item's checked state through UI + /// Automation: Avalonia's Win32 automation bridge does not currently surface the UIA Toggle pattern for + /// MenuItem to native automation clients (its cross-platform MenuItemAutomationPeer + /// implements IToggleProvider internally, but that provider is never reachable from a real UI + /// Automation client such as NovaWindows/System.Windows.Automation, which always throws "Unsupported + /// Pattern" attempting to read it) - so content presence is the only reliable ground truth available. + /// + /// The top-level menu's display name (e.g. "View"). + /// The child menu item's AutomationProperties.AutomationId value. + /// + /// An automation id unique to a control that is always present while the panel is open and the active + /// tab (not conditionally hidden by panel content state, e.g. an always-visible toolbar button), used to + /// confirm the panel is genuinely shown or hidden. + /// + private void AssertMenuItemTogglesPanel(string topLevelMenuName, string menuItemAutomationId, string panelControlAutomationId) + { + var reopened = false; + try + { + // Act 1 - click once; the panel starts open, so this closes it. + ClickMenuItem(topLevelMenuName, menuItemAutomationId); + var closed = WaitUntil(() => _session.FindElements(MobileBy.AccessibilityId(panelControlAutomationId)).Count == 0); + Assert.True(closed, $"The panel control '{panelControlAutomationId}' was still present after closing it via '{menuItemAutomationId}'."); + + // Act 2 - click again; the panel is now closed, so this reopens and focuses it, restoring the + // originally-open state this test found the panel in. + ClickMenuItem(topLevelMenuName, menuItemAutomationId); + reopened = WaitUntil(() => _session.FindElements(MobileBy.AccessibilityId(panelControlAutomationId)).Count > 0); + Assert.True(reopened, $"The panel control '{panelControlAutomationId}' did not reappear after reopening it via '{menuItemAutomationId}'."); + Assert.True(_session.FindElement(MobileBy.AccessibilityId(panelControlAutomationId)).Displayed); + } + finally + { + // Guarantee the panel ends up open even if an assertion above failed partway through, so a failure + // here never leaks a closed panel into later tests sharing this AppFixture session. This is + // idempotent on current content presence rather than a fixed click count: if the close assertion + // failed, the panel is presumably still open and no further action is needed; if the reopen + // assertion failed, the panel is presumably still closed and one more click restores it. + if (!reopened && _session.FindElements(MobileBy.AccessibilityId(panelControlAutomationId)).Count == 0) + { + ClickMenuItem(topLevelMenuName, menuItemAutomationId); + } + } + } + + /// + /// Opens the named top-level menu, clicks the child item by automation id (which also closes the menu, + /// matching normal menu-click behavior), then returns. + /// + /// The top-level menu's display name (e.g. "View"). + /// The child menu item's AutomationProperties.AutomationId value. + private void ClickMenuItem(string topLevelMenuName, string menuItemAutomationId) + { + _session.FindElement(MobileBy.Name(topLevelMenuName)).Click(); + _session.FindElement(MobileBy.AccessibilityId(menuItemAutomationId)).Click(); + } + /// /// Validates that the View menu's "Custom View Builder..." item, found by the /// ViewBuilderDialogMenuItem automation id, is discoverable and enabled. Not clicked, since the