#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_monitor_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); OnlineLogicMonitorService online_monitor_service(virtual_repository); RuntimeModeService runtime_mode_service( project_service, simulation_service, online_monitor_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()); QLabel *logic_label = controller.runtimeMonitorWidget() ->findChild(QStringLiteral("logicLabel")); QLabel *notice_label = controller.runtimeMonitorWidget() ->findChild(QStringLiteral("logicNoticeLabel")); require(logic_label != nullptr && notice_label != nullptr, "runtime monitor must expose its local trace labels"); require(logic_label->text() == QStringLiteral("梯形图运行状态") && !notice_label->isVisible(), "offline runtime must keep the normal ladder trace heading"); controller.runtimeMonitorWidget()->setMode( ApplicationMode::OnlineRunning, PlcConnectionState::Connected); require(logic_label->text() == QStringLiteral("本地推算轨迹") && notice_label->isVisible() && notice_label->text().contains(QStringLiteral("不写入 PLC 程序或 M/D")) && notice_label->text().contains(QStringLiteral("HMI 和自由监控仍可写 PLC")), "online runtime must explain the read-only local trace boundary"); controller.runtimeMonitorWidget()->setMode( ApplicationMode::OfflineRunning, PlcConnectionState::Disconnected); 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; }