From 1ba67589f53393ab1b9c34bf845e89571fcc1f33 Mon Sep 17 00:00:00 2001 From: suyu <1643689728@qq.com> Date: Mon, 24 Aug 2026 19:21:49 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B8=85=E7=90=86=E9=80=80=E5=87=BA?= =?UTF-8?q?=E4=BB=BF=E7=9C=9F=E5=90=8E=E7=9A=84=E6=97=A7=E8=BD=A8=E8=BF=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/ui/logic_editor_widget.cpp | 5 + app/src/ui/logic_editor_widget.h | 2 + app/src/ui/runtime_panel_controller.cpp | 6 + app/tests/runtime_panel_controller_tests.cpp | 143 +++++++++++++++++++ app/tests/runtime_panel_controller_tests.pro | 48 +++++++ app/tests/tests.pro | 2 + scripts/run_qt_tests.ps1 | 1 + 7 files changed, 207 insertions(+) create mode 100644 app/tests/runtime_panel_controller_tests.cpp create mode 100644 app/tests/runtime_panel_controller_tests.pro diff --git a/app/src/ui/logic_editor_widget.cpp b/app/src/ui/logic_editor_widget.cpp index 9f21769..8a70931 100644 --- a/app/src/ui/logic_editor_widget.cpp +++ b/app/src/ui/logic_editor_widget.cpp @@ -1045,6 +1045,11 @@ void LogicEditorWidget::clearRuntimeTrace() reloadLogic(); } +bool LogicEditorWidget::runtimeTraceEnabled() const +{ + return runtime_trace_enabled_; +} + void LogicEditorWidget::reloadLogic() { // 逻辑或选择变化后重新计算网格布局和可点击插入目标 diff --git a/app/src/ui/logic_editor_widget.h b/app/src/ui/logic_editor_widget.h index 0915290..8d67f5d 100644 --- a/app/src/ui/logic_editor_widget.h +++ b/app/src/ui/logic_editor_widget.h @@ -51,6 +51,8 @@ public: const std::string &fault_node_id = {}); /** 清除当前运行轨迹和故障节点标记 */ void clearRuntimeTrace(); + /** 返回当前是否正在显示运行轨迹 */ + bool runtimeTraceEnabled() const; /** 根据当前逻辑模型重建梯形图场景 */ void reloadLogic(); /** 选中指定节点并滚动到可见区域 */ diff --git a/app/src/ui/runtime_panel_controller.cpp b/app/src/ui/runtime_panel_controller.cpp index 4d791e9..31eba16 100644 --- a/app/src/ui/runtime_panel_controller.cpp +++ b/app/src/ui/runtime_panel_controller.cpp @@ -288,6 +288,12 @@ void RuntimePanelController::handleSimulationStateChanged() void RuntimePanelController::handleScanCompleted() { + // 排队的扫描信号可能晚于退出操作到达,编辑态不得恢复旧运行轨迹 + if (runtime_mode_service_.mode() != ApplicationMode::OfflineRunning + || runtime_mode_service_.simulationState() != SimulationState::Running) + { + return; + } const std::string logic_id = current_logic_id_(); logic_editor_widget_.setRuntimeTrace( runtime_mode_service_.offlineSimulationService() diff --git a/app/tests/runtime_panel_controller_tests.cpp b/app/tests/runtime_panel_controller_tests.cpp new file mode 100644 index 0000000..c62532a --- /dev/null +++ b/app/tests/runtime_panel_controller_tests.cpp @@ -0,0 +1,143 @@ +#include "domain/virtual_register_repository.h" +#include "services/alarm_service.h" +#include "services/hmi_editor_service.h" +#include "services/hmi_navigation_service.h" +#include "services/hmi_runtime_service.h" +#include "services/logic_editor_service.h" +#include "services/offline_simulation_service.h" +#include "services/project_service.h" +#include "services/register_monitor_service.h" +#include "services/runtime_mode_service.h" +#include "support/test_support.h" +#include "ui/hmi_editor_widget.h" +#include "ui/logic_editor_widget.h" +#include "ui/runtime_panel_controller.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace { + +using TestProjectStorage = TestSupport::InMemoryProjectStorage; +using TestSupport::require; + +ControlLogic makeAlwaysOnLogic() +{ + ControlLogic logic; + logic.id = "queued-trace-logic"; + logic.name = "Queued trace logic"; + + LadderRung rung; + rung.id = "always-on-rung"; + rung.name = "Always on"; + rung.condition = ConditionExpression::fromWire("always-on-wire", 10); + + LogicNode output; + output.id = "always-on-output"; + output.config = CoilNodeConfig{ + RegisterAddress{RegisterArea::M, 1}, CoilMode::Normal}; + rung.output = output; + logic.rungs.push_back(rung); + return logic; +} + +void testQueuedOfflineTraceIsIgnoredAfterReturningToEditing() +{ + TestProjectStorage storage; + ProjectService project_service(storage); + VirtualRegisterRepository virtual_repository; + OfflineSimulationService simulation_service(virtual_repository); + RuntimeModeService runtime_mode_service(project_service, simulation_service); + HmiEditorService hmi_editor_service(project_service); + HmiRuntimeService hmi_runtime_service(virtual_repository); + LogicEditorService logic_editor_service(project_service); + HmiNavigationService hmi_navigation_service(project_service); + AlarmService alarm_service(project_service, virtual_repository); + RegisterMonitorService register_monitor_service(virtual_repository); + + const ControlLogic logic = makeAlwaysOnLogic(); + project_service.editProject().controlLogics.push_back(logic); + + QWidget parent; + HmiEditorWidget hmi_editor_widget( + hmi_editor_service, hmi_runtime_service, alarm_service, &parent); + LogicEditorWidget logic_editor_widget(logic_editor_service, &parent); + QLabel executor_status_label(&parent); + std::string current_logic_id = logic.id; + logic_editor_widget.setLogicId(current_logic_id); + + RuntimePanelController controller( + parent, + runtime_mode_service, + project_service, + hmi_editor_service, + hmi_runtime_service, + logic_editor_service, + hmi_navigation_service, + alarm_service, + register_monitor_service, + hmi_editor_widget, + logic_editor_widget, + executor_status_label, + [¤t_logic_id] { return current_logic_id; }, + [¤t_logic_id](const std::string &logic_id) + { + current_logic_id = logic_id; + }, + [](const std::string &) {}, + [](const QString &, int) {}, + [](const QString &) {}, + [] {}); + controller.configure(); + + require(runtime_mode_service.enterOfflineRunning().succeeded, + "offline simulation must start for queued trace regression"); + controller.enterRuntime( + {}, logic.id, runtime_mode_service.mode(), + runtime_mode_service.plcConnectionState()); + require(simulation_service.executeOnce().succeeded, + "offline simulation must produce a trace before editing"); + require(simulation_service.traceSnapshot() + .forLogic(logic.id).rungValues.at("always-on-rung"), + "the queued trace must contain an energized rung"); + + require(runtime_mode_service.enterEditing().succeeded, + "offline simulation must return to editing before queued delivery"); + controller.leaveRuntime( + runtime_mode_service.mode(), runtime_mode_service.plcConnectionState()); + logic_editor_widget.clearRuntimeTrace(); + require(!logic_editor_widget.runtimeTraceEnabled(), + "editing transition must initially clear the runtime trace"); + + QCoreApplication::processEvents(QEventLoop::AllEvents); + require(!logic_editor_widget.runtimeTraceEnabled(), + "a queued offline scan must not restore the trace after returning to editing"); +} + +} // namespace + +int main(int argc, char *argv[]) +{ + qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("offscreen")); + QApplication application(argc, argv); + try + { + testQueuedOfflineTraceIsIgnoredAfterReturningToEditing(); + } + catch (const std::exception &error) + { + std::cerr << "runtime panel controller tests failed: " + << error.what() << '\n'; + return 1; + } + + std::cout << "runtime panel controller tests passed\n"; + return 0; +} diff --git a/app/tests/runtime_panel_controller_tests.pro b/app/tests/runtime_panel_controller_tests.pro new file mode 100644 index 0000000..862f2c7 --- /dev/null +++ b/app/tests/runtime_panel_controller_tests.pro @@ -0,0 +1,48 @@ +include(pri/test_defaults.pri) +include(pri/test_layers.pri) + +TARGET = runtime_panel_controller_tests +QT += core gui widgets +CONFIG += testcase + +SOURCES += \ + runtime_panel_controller_tests.cpp \ + $$DOMAIN_ALL_SOURCES \ + $$SERVICE_PROJECT_SOURCES \ + $$SERVICE_ALARM_SOURCES \ + $$SERVICE_HMI_SOURCES \ + $$SERVICE_LOGIC_SOURCES \ + $$SERVICE_OFFLINE_SOURCES \ + $$SERVICE_RUNTIME_SOURCES \ + $$SERVICE_MONITOR_SOURCES \ + ../src/ui/hmi_editor_widget.cpp \ + ../src/ui/logic_editor_widget.cpp \ + ../src/ui/free_monitor_widget.cpp \ + ../src/ui/runtime_monitor_window.cpp \ + ../src/ui/runtime_monitor_widget.cpp \ + ../src/ui/toolbar_icon_factory.cpp \ + ../src/ui/runtime_panel_controller.cpp + +HEADERS += \ + $$DOMAIN_ALL_HEADERS \ + $$SERVICE_PROJECT_HEADERS \ + $$SERVICE_ALARM_HEADERS \ + $$SERVICE_HMI_HEADERS \ + $$SERVICE_LOGIC_HEADERS \ + $$SERVICE_OFFLINE_HEADERS \ + $$SERVICE_RUNTIME_HEADERS \ + $$SERVICE_MONITOR_HEADERS \ + ../src/services/plc_communication_gateway.h \ + ../src/ui/hmi_editor_widget.h \ + ../src/ui/logic_editor_widget.h \ + ../src/ui/free_monitor_widget.h \ + ../src/ui/runtime_monitor_window.h \ + ../src/ui/runtime_monitor_widget.h \ + ../src/ui/toolbar_icon_factory.h \ + ../src/ui/runtime_panel_controller.h \ + $$TEST_SUPPORT_HEADERS + +FORMS += \ + ../src/ui/free_monitor_widget.ui \ + ../src/ui/runtime_monitor_window.ui \ + ../src/ui/runtime_monitor_widget.ui diff --git a/app/tests/tests.pro b/app/tests/tests.pro index 25f7250..27e09d2 100644 --- a/app/tests/tests.pro +++ b/app/tests/tests.pro @@ -11,6 +11,7 @@ SUBDIRS += \ project_management \ register_monitor \ runtime_mode \ + runtime_panel_controller \ plc_runtime domain.file = domain_tests.pro @@ -21,4 +22,5 @@ offline_simulation.file = offline_simulation_service_tests.pro project_management.file = project_management_tests.pro register_monitor.file = register_monitor_service_tests.pro runtime_mode.file = runtime_mode_service_tests.pro +runtime_panel_controller.file = runtime_panel_controller_tests.pro plc_runtime.file = plc_runtime_tests.pro diff --git a/scripts/run_qt_tests.ps1 b/scripts/run_qt_tests.ps1 index 5e915d8..4f0e398 100644 --- a/scripts/run_qt_tests.ps1 +++ b/scripts/run_qt_tests.ps1 @@ -33,6 +33,7 @@ $functionalTargets = @( 'project_management_tests', 'register_monitor_service_tests', 'runtime_mode_service_tests', + 'runtime_panel_controller_tests', 'plc_runtime_tests' ) $performanceTargets = @('performance_tests')