diff --git a/src/Extensions/ACAT.Extensions.UI/Scanners/DashboardAppScanner.cs b/src/Extensions/ACAT.Extensions.UI/Scanners/DashboardAppScanner.cs index 2cc2cda0..dd57472a 100644 --- a/src/Extensions/ACAT.Extensions.UI/Scanners/DashboardAppScanner.cs +++ b/src/Extensions/ACAT.Extensions.UI/Scanners/DashboardAppScanner.cs @@ -4,6 +4,8 @@ using ACAT.Core.PanelManagement.CommandDispatcher; using ACAT.Core.PanelManagement.Common; using ACAT.Core.PanelManagement.Interfaces; +using ACAT.Core.Patterns.CQRS; +using ACAT.Core.Patterns.CQRS.Samples; using ACAT.Core.UserControlManagement; using ACAT.Core.UserControlManagement.Interfaces; using ACAT.Core.Utility; @@ -206,8 +208,19 @@ public bool ShowTalkPanel(WordPredictionModes mode) PredictionMode = mode }; - // create the panel instance - Form form = PanelManager.Instance.CreatePanel("TalkApplicationScanner", startupArg); + // CQRS: Use command handler instead of direct singleton access + var createPanelHandler = Context.ServiceProvider?.GetService(typeof(ICommandHandler)) as ICommandHandler; + Form form; + if (createPanelHandler != null) + { + var command = new CreatePanelCommand("TalkApplicationScanner", startupArg: startupArg); + createPanelHandler.Handle(command); + form = command.CreatedPanel as Form; + } + else + { + form = PanelManager.Instance.CreatePanel("TalkApplicationScanner", startupArg); + } if (form == null) { _logger.LogError("Could not create TalkApplicationScanner panel."); diff --git a/src/Libraries/ACATCore.Tests/CQRSHandlerTests.cs b/src/Libraries/ACATCore.Tests/CQRSHandlerTests.cs new file mode 100644 index 00000000..d0d9040f --- /dev/null +++ b/src/Libraries/ACATCore.Tests/CQRSHandlerTests.cs @@ -0,0 +1,203 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013-2019; 2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// +// +// CQRSHandlerTests.cs +// +// Unit tests for CQRS command handler resolution paths: +// 1. Handler available via DI (DI path) +// 2. Handler not available – caller falls back to legacy singleton (fallback path) +// +//////////////////////////////////////////////////////////////////////////// + +using ACAT.Core.ActuatorManagement; +using ACAT.Core.ActuatorManagement.Interfaces; +using ACAT.Core.PanelManagement; +using ACAT.Core.PanelManagement.Common; +using ACAT.Core.PanelManagement.Interfaces; +using ACAT.Core.Patterns.CQRS; +using ACAT.Core.Patterns.CQRS.Samples; +using ACATCore.Tests.Mocks; +using Moq; +using System.Windows.Forms; +using Xunit; + +namespace ACATCore.Tests +{ + /// + /// Unit tests for the CQRS and + /// , covering both the + /// DI-available and fallback (handler-null) resolution paths. + /// + public class CQRSHandlerTests + { + // ---------------------------------------------------------------- + // CreatePanelCommandHandler – DI path + // ---------------------------------------------------------------- + + [Fact] + public void CreatePanelCommandHandler_WhenHandlerAvailable_CallsPanelManagerAndSetsCreatedPanel() + { + // Arrange + var mockPanel = new Mock
(); + var panelManagerMock = ManagerMocks.CreatePanelManager(); + panelManagerMock + .Setup(m => m.CreatePanel(It.IsAny())) + .Returns(mockPanel.Object); + + var handler = new CreatePanelCommandHandler(panelManagerMock.Object); + var command = new CreatePanelCommand("TestScanner"); + + // Act + handler.Handle(command); + + // Assert – panel manager was called with the correct class name + panelManagerMock.Verify(m => m.CreatePanel("TestScanner"), Times.Once); + } + + [Fact] + public void CreatePanelCommandHandler_WithTitleAndStartupArg_CallsCorrectOverload() + { + // Arrange + var mockPanel = new Mock(); + var startupArg = new StartupArg("TestScanner"); + var panelManagerMock = ManagerMocks.CreatePanelManager(); + panelManagerMock + .Setup(m => m.CreatePanel( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(mockPanel.Object); + + var handler = new CreatePanelCommandHandler(panelManagerMock.Object); + var command = new CreatePanelCommand("TestScanner", "My Title", startupArg); + + // Act + handler.Handle(command); + + // Assert – overload with title and startupArg was invoked + panelManagerMock.Verify( + m => m.CreatePanel("TestScanner", "My Title", startupArg), + Times.Once); + } + + // ---------------------------------------------------------------- + // CreatePanelCommandHandler – fallback path (handler is null) + // ---------------------------------------------------------------- + + [Fact] + public void CreatePanelCommand_WhenHandlerIsNull_CreatedPanelRemainsNull() + { + // Arrange – no handler, simulate the null-handler fallback path + ICommandHandler handler = null; + var command = new CreatePanelCommand("TestScanner"); + + // Act – caller would use legacy fallback; here we just verify + // that the command remains in its initial state (CreatedPanel == null) + // and no exception is thrown when handler is not invoked. + if (handler != null) + { + handler.Handle(command); + } + + // Assert + Assert.Null(command.CreatedPanel); + } + + // ---------------------------------------------------------------- + // HandleActuatorSwitchCommandHandler – DI path + // ---------------------------------------------------------------- + + [Fact] + public void HandleActuatorSwitchCommandHandler_Pause_CallsActuatorManagerPause() + { + // Arrange + var actuatorManagerMock = ManagerMocks.CreateActuatorManager(); + var handler = new HandleActuatorSwitchCommandHandler(actuatorManagerMock.Object); + var command = new HandleActuatorSwitchCommand(ActuatorSwitchAction.Pause); + + // Act + handler.Handle(command); + + // Assert + actuatorManagerMock.Verify(m => m.Pause(), Times.Once); + actuatorManagerMock.Verify(m => m.Resume(), Times.Never); + } + + [Fact] + public void HandleActuatorSwitchCommandHandler_Resume_CallsActuatorManagerResume() + { + // Arrange + var actuatorManagerMock = ManagerMocks.CreateActuatorManager(); + var handler = new HandleActuatorSwitchCommandHandler(actuatorManagerMock.Object); + var command = new HandleActuatorSwitchCommand(ActuatorSwitchAction.Resume); + + // Act + handler.Handle(command); + + // Assert + actuatorManagerMock.Verify(m => m.Resume(), Times.Once); + actuatorManagerMock.Verify(m => m.Pause(), Times.Never); + } + + // ---------------------------------------------------------------- + // HandleActuatorSwitchCommandHandler – fallback path (handler is null) + // ---------------------------------------------------------------- + + [Fact] + public void HandleActuatorSwitchCommand_WhenHandlerIsNull_NoExceptionThrown() + { + // Arrange – simulate no DI handler available (null fallback path) + ICommandHandler handler = null; + var command = new HandleActuatorSwitchCommand(ActuatorSwitchAction.Pause); + + // Act – caller would use legacy fallback; no exception should occur + // when the handler check is performed and handler is null. + var exceptionThrown = false; + try + { + if (handler != null) + { + handler.Handle(command); + } + // legacy fallback would call Context.AppActuatorManager.Pause() here + } + catch + { + exceptionThrown = true; + } + + // Assert + Assert.False(exceptionThrown); + } + + // ---------------------------------------------------------------- + // ActuatorBase property injection + // ---------------------------------------------------------------- + + [Fact] + public void ActuatorBase_CreatePanelHandlerProperty_CanBeSetAndRetrieved() + { + // Arrange + var panelManagerMock = ManagerMocks.CreatePanelManager(); + var handlerMock = new Mock>(); + var actuator = new TestActuator(); + + // Act + actuator.CreatePanelHandler = handlerMock.Object; + + // Assert + Assert.Same(handlerMock.Object, actuator.CreatePanelHandler); + } + + /// + /// Minimal concrete actuator used only for unit tests. + /// + private class TestActuator : ActuatorBase + { + public override IActuatorSwitch CreateSwitch() => null; + } + } +} diff --git a/src/Libraries/ACATCore/ActuatorManagement/ActuatorBase.cs b/src/Libraries/ACATCore/ActuatorManagement/ActuatorBase.cs index a4ca8f7c..498e35c2 100644 --- a/src/Libraries/ACATCore/ActuatorManagement/ActuatorBase.cs +++ b/src/Libraries/ACATCore/ActuatorManagement/ActuatorBase.cs @@ -21,6 +21,8 @@ using ACAT.Core.Extensions; using ACAT.Core.PanelManagement; using ACAT.Core.PanelManagement.Interfaces; +using ACAT.Core.Patterns.CQRS; +using ACAT.Core.Patterns.CQRS.Samples; using ACAT.Core.PreferencesManagement.Interfaces; using ACAT.Core.Utility; using Microsoft.Extensions.Logging; @@ -58,6 +60,11 @@ public abstract class ActuatorBase : IActuator /// private bool _disposed; + /// + /// Backing field for . + /// + private ICommandHandler _createPanelHandler; + /// /// Initializes a new instance of the ActuatorBase class /// @@ -166,6 +173,20 @@ public ICollection Switches protected State actuatorState { get; set; } public Guid Id => Descriptor.Id; + /// + /// Injectable command handler for creating panels. When set, panel creation + /// uses CQRS instead of direct singleton access. Falls back to + /// when not set. + /// Resolved lazily from if available. + /// + public ICommandHandler CreatePanelHandler + { + get => _createPanelHandler ?? + (Context.ServiceProvider?.GetService(typeof(ICommandHandler)) + as ICommandHandler); + set => _createPanelHandler = value; + } + /// /// Class factory to create a switch. Override this in the /// derived classes to enable creating switches that are specific to the @@ -658,7 +679,23 @@ protected bool ShowDefaultScanTimingsConfigureDialog() return false; } - Form form = PanelManager.Instance.CreatePanel(CoreGlobals.AppPreferences.DefaultScanTimingsConfigurePanelName, "Adjust Scanning Speed"); + Form form; + var handler = CreatePanelHandler; + if (handler != null) + { + var command = new CreatePanelCommand( + CoreGlobals.AppPreferences.DefaultScanTimingsConfigurePanelName, + "Adjust Scanning Speed"); + handler.Handle(command); + form = command.CreatedPanel as Form; + } + else + { + form = PanelManager.Instance.CreatePanel( + CoreGlobals.AppPreferences.DefaultScanTimingsConfigurePanelName, + "Adjust Scanning Speed"); + } + if (form != null) { Context.AppPanelManager.ShowDialog(form as IPanel); @@ -675,7 +712,23 @@ protected bool ShowDefaultTryoutDialog() return false; } - Form form = PanelManager.Instance.CreatePanel(CoreGlobals.AppPreferences.DefaultTryoutPanelName, "Switch Tryout"); + Form form; + var handler = CreatePanelHandler; + if (handler != null) + { + var command = new CreatePanelCommand( + CoreGlobals.AppPreferences.DefaultTryoutPanelName, + "Switch Tryout"); + handler.Handle(command); + form = command.CreatedPanel as Form; + } + else + { + form = PanelManager.Instance.CreatePanel( + CoreGlobals.AppPreferences.DefaultTryoutPanelName, + "Switch Tryout"); + } + if (form != null) { Context.AppPanelManager.ShowDialog(form as IPanel); diff --git a/src/Libraries/ACATCore/PanelManagement/Common/DialogCommon.cs b/src/Libraries/ACATCore/PanelManagement/Common/DialogCommon.cs index d0bccfd5..a9640813 100644 --- a/src/Libraries/ACATCore/PanelManagement/Common/DialogCommon.cs +++ b/src/Libraries/ACATCore/PanelManagement/Common/DialogCommon.cs @@ -9,19 +9,21 @@ using ACAT.Core.AnimationManagement; using ACAT.Core.Interpreter; using ACAT.Core.PanelManagement.Interfaces; +using ACAT.Core.PanelManagement.PanelConfig; using ACAT.Core.PanelManagement.Utils; +using ACAT.Core.Patterns.CQRS; +using ACAT.Core.Patterns.CQRS.Samples; using ACAT.Core.ThemeManagement; using ACAT.Core.Utility; using ACAT.Core.WidgetManagement; using ACAT.Core.WidgetManagement.Interfaces; +using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Drawing; using System.Security.Permissions; using System.Windows.Automation; using System.Windows.Forms; -using ACAT.Core.PanelManagement.PanelConfig; -using Microsoft.Extensions.Logging; namespace ACAT.Core.PanelManagement.Common { @@ -67,6 +69,11 @@ public class DialogCommon : IDisposable, IPanelCommon /// private bool _disposed; + /// + /// Backing field for . + /// + private ICommandHandler _createPanelHandler; + /// /// Name of the form in the Panel config map /// @@ -144,6 +151,20 @@ public PanelAnimationManager AnimationManager public Widget RootWidget { get { return _rootWidget; } } + /// + /// Injectable command handler for creating panels. When set, panel creation + /// uses CQRS instead of direct singleton access. Falls back to + /// when not set. + /// Resolved lazily from if available. + /// + public ICommandHandler CreatePanelHandler + { + get => _createPanelHandler ?? + (Context.ServiceProvider?.GetService(typeof(ICommandHandler)) + as ICommandHandler); + set => _createPanelHandler = value; + } + /// /// Returns the synchronization object /// @@ -393,7 +414,19 @@ private void createAndShowScannerForWidget(Widget widget) StartupArg startupArg = createStartupArgForScanner(widget); _logger?.LogDebug("Creating Panel {PanelName}", widget.Panel); - Form panel = Context.AppPanelManager.CreatePanel(widget.Panel, string.Empty, startupArg); + // CQRS: Use command handler instead of direct singleton access + Form panel; + var handler = CreatePanelHandler; + if (handler != null) + { + var command = new CreatePanelCommand(widget.Panel, string.Empty, startupArg); + handler.Handle(command); + panel = command.CreatedPanel as Form; + } + else + { + panel = Context.AppPanelManager.CreatePanel(widget.Panel, string.Empty, startupArg); + } var child = panel as IScannerPanel; if (child != null) { diff --git a/src/Libraries/ACATCore/PanelManagement/Common/ScannerCommon.cs b/src/Libraries/ACATCore/PanelManagement/Common/ScannerCommon.cs index 48127e9a..702acb87 100644 --- a/src/Libraries/ACATCore/PanelManagement/Common/ScannerCommon.cs +++ b/src/Libraries/ACATCore/PanelManagement/Common/ScannerCommon.cs @@ -14,6 +14,8 @@ using ACAT.Core.PanelManagement.Interfaces; using ACAT.Core.PanelManagement.PanelConfig; using ACAT.Core.PanelManagement.Utils; +using ACAT.Core.Patterns.CQRS; +using ACAT.Core.Patterns.CQRS.Samples; using ACAT.Core.ThemeManagement; using ACAT.Core.UserControlManagement; using ACAT.Core.Utility; @@ -111,6 +113,11 @@ public class ScannerCommon : IDisposable, IPanelCommon /// private bool _isPaused; + /// + /// Backing field for . + /// + private ICommandHandler _createPanelHandler; + /// /// is scanner in preview mode? /// @@ -237,6 +244,20 @@ public bool IsPaused get { return _isPaused; } } + /// + /// Injectable command handler for creating panels. When set, panel creation + /// uses CQRS instead of direct singleton access. Falls back to + /// when not set. + /// Resolved lazily from if available. + /// + public ICommandHandler CreatePanelHandler + { + get => _createPanelHandler ?? + (Context.ServiceProvider?.GetService(typeof(ICommandHandler)) + as ICommandHandler); + set => _createPanelHandler = value; + } + /// /// Helps with managing the position and size of the scanner panel /// @@ -1326,7 +1347,20 @@ private void Interpreter_EvtShowPopup(object sender, InterpreterEventArgs e) ScannerForm.InvokeIfRequired(() => { - IPanel panel = Context.AppPanelManager.CreatePanel(scannerName, title) as IPanel; + // CQRS: Use command handler instead of direct singleton access + IPanel panel; + var handler = CreatePanelHandler; + if (handler != null) + { + var command = new CreatePanelCommand(scannerName, title); + handler.Handle(command); + panel = command.CreatedPanel; + } + else + { + panel = Context.AppPanelManager.CreatePanel(scannerName, title) as IPanel; + } + if (panel != null) { Context.AppPanelManager.ShowPopup(ScannerForm as IPanel, panel); diff --git a/src/Libraries/ACATExtension/CommandHandlers/ShowScreenLockHandler.cs b/src/Libraries/ACATExtension/CommandHandlers/ShowScreenLockHandler.cs index e0074cc9..094cba79 100644 --- a/src/Libraries/ACATExtension/CommandHandlers/ShowScreenLockHandler.cs +++ b/src/Libraries/ACATExtension/CommandHandlers/ShowScreenLockHandler.cs @@ -8,6 +8,8 @@ using ACAT.Core.PanelManagement; using ACAT.Core.PanelManagement.CommandDispatcher; using ACAT.Core.PanelManagement.Interfaces; +using ACAT.Core.Patterns.CQRS; +using ACAT.Core.Patterns.CQRS.Samples; using ACAT.Core.Utility; using ACAT.Extension.UI; using ACATResources; @@ -46,17 +48,30 @@ public override bool Execute(ref bool handled) { if (DialogUtils.ConfirmScanner(form as IPanel, StringResources.LockTheScreen)) { - Form screenLockForm = PanelManager.Instance.CreatePanel("ScreenLockScanner", "Lock Screen"); - if (screenLockForm != null) - { - WindowActivityMonitor.Pause(); - Context.AppPanelManager.ShowDialog(screenLockForm as IPanel); - WindowActivityMonitor.Resume(); - } - else - { - retVal = false; - } + // CQRS: Use command handler instead of direct singleton access + var createPanelHandler = Context.ServiceProvider?.GetService(typeof(ICommandHandler)) as ICommandHandler; + Form screenLockForm; + if (createPanelHandler != null) + { + var command = new CreatePanelCommand("ScreenLockScanner", "Lock Screen"); + createPanelHandler.Handle(command); + screenLockForm = command.CreatedPanel as Form; + } + else + { + screenLockForm = PanelManager.Instance.CreatePanel("ScreenLockScanner", "Lock Screen"); + } + + if (screenLockForm != null) + { + WindowActivityMonitor.Pause(); + Context.AppPanelManager.ShowDialog(screenLockForm as IPanel); + WindowActivityMonitor.Resume(); + } + else + { + retVal = false; + } } })); diff --git a/src/Libraries/ACATExtension/CommandHandlers/TalkWindowHandler.cs b/src/Libraries/ACATExtension/CommandHandlers/TalkWindowHandler.cs index 2d19059c..21320529 100644 --- a/src/Libraries/ACATExtension/CommandHandlers/TalkWindowHandler.cs +++ b/src/Libraries/ACATExtension/CommandHandlers/TalkWindowHandler.cs @@ -9,6 +9,9 @@ using ACAT.Core.PanelManagement; using ACAT.Core.PanelManagement.CommandDispatcher; using ACAT.Core.PanelManagement.Interfaces; +using ACAT.Core.Patterns.CQRS; +using ACAT.Core.Patterns.CQRS.Samples; +using ACAT.Core.Utility; using System; using System.Windows.Forms; @@ -41,7 +44,20 @@ public override bool Execute(ref bool handled) switch (Command) { case "CmdTalkApp": - Form form = PanelManager.Instance.CreatePanel("TalkApplicationScanner"); + // CQRS: Use command handler instead of direct singleton access + var createPanelHandler = Context.ServiceProvider?.GetService(typeof(ICommandHandler)) as ICommandHandler; + Form form; + if (createPanelHandler != null) + { + var command = new CreatePanelCommand("TalkApplicationScanner"); + createPanelHandler.Handle(command); + form = command.CreatedPanel as Form; + } + else + { + form = PanelManager.Instance.CreatePanel("TalkApplicationScanner"); + } + if (form != null) { // Add ad-hoc agent that will handle the form