综合平台编程器项目的远程存储
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 

222 рядки
9.2 KiB

  1. #include "domain/project_storage.h"
  2. #include "domain/register_repository.h"
  3. #include "services/hmi_editor_service.h"
  4. #include "services/hmi_runtime_service.h"
  5. #include "services/logic_editor_service.h"
  6. #include "services/offline_simulation_service.h"
  7. #include "services/project_service.h"
  8. #include "services/runtime_mode_service.h"
  9. #include "ui/hmi_editor_widget.h"
  10. #include "ui/logic_editor_widget.h"
  11. #include "ui/main_window.h"
  12. #include <QAction>
  13. #include <QApplication>
  14. #include <QDockWidget>
  15. #include <QLabel>
  16. #include <QLineEdit>
  17. #include <iostream>
  18. #include <stdexcept>
  19. #include <string>
  20. namespace {
  21. class TestProjectStorage final : public ProjectStorage
  22. {
  23. public:
  24. // 主窗口测试使用无副作用存储实现,隔离文件对 UI 行为的影响
  25. ProjectSaveResult save(const Project &, const std::string &) override
  26. {
  27. return {true, ProjectStorageError::None, {}};
  28. }
  29. ProjectLoadResult load(const std::string &) override
  30. {
  31. return {false, {}, ProjectStorageError::FileReadFailed, {}};
  32. }
  33. };
  34. void require(bool condition, const std::string &message)
  35. {
  36. if (!condition)
  37. {
  38. throw std::runtime_error(message);
  39. }
  40. }
  41. template<typename ObjectType>
  42. ObjectType *requiredChild(MainWindow &window, const char *name)
  43. {
  44. // 通过 objectName 取得 Designer 组件,缺失时给出明确测试失败信息
  45. ObjectType *child = window.findChild<ObjectType *>(QString::fromLatin1(name));
  46. require(child != nullptr, std::string("missing UI object ") + name);
  47. return child;
  48. }
  49. void testModeActionsControlEditingAvailability()
  50. {
  51. // 验证模式动作会同步禁用编辑入口,并在失败时恢复当前选择
  52. TestProjectStorage storage;
  53. ProjectService project_service(storage);
  54. HmiEditorService editor_service(project_service);
  55. LogicEditorService logic_editor_service(project_service);
  56. VirtualRegisterRepository repository;
  57. HmiRuntimeService runtime_service(repository);
  58. OfflineSimulationService simulation_service(repository);
  59. RuntimeModeService mode_service(project_service, simulation_service);
  60. MainWindow window(
  61. mode_service,
  62. project_service,
  63. editor_service,
  64. logic_editor_service,
  65. runtime_service);
  66. window.resize(1000, 640);
  67. window.show();
  68. QApplication::processEvents();
  69. QAction *editing_action = requiredChild<QAction>(window, "editingModeAction");
  70. QAction *offline_action = requiredChild<QAction>(window, "offlineModeAction");
  71. QAction *online_action = requiredChild<QAction>(window, "onlineModeAction");
  72. QAction *add_button_action = requiredChild<QAction>(window, "addButtonAction");
  73. QAction *delete_control_action = requiredChild<QAction>(window, "deleteControlAction");
  74. QAction *add_normally_open_action = requiredChild<QAction>(
  75. window, "addNormallyOpenAction");
  76. QAction *add_normal_coil_action = requiredChild<QAction>(
  77. window, "addNormalCoilAction");
  78. QAction *parallel_insert_action = requiredChild<QAction>(
  79. window, "parallelInsertAction");
  80. QDockWidget *project_dock = requiredChild<QDockWidget>(window, "projectDock");
  81. QDockWidget *properties_dock = requiredChild<QDockWidget>(window, "propertiesDock");
  82. QLabel *selection = requiredChild<QLabel>(window, "selectionValueLabel");
  83. QLineEdit *text_edit = requiredChild<QLineEdit>(window, "controlTextEdit");
  84. HmiEditorWidget *hmi_editor = requiredChild<HmiEditorWidget>(
  85. window, "hmiEditorWidget");
  86. QLabel *executor_status = requiredChild<QLabel>(window, "executorStatusLabel");
  87. const qreal compact_scale = hmi_editor->transform().m11();
  88. window.resize(1600, 900);
  89. QApplication::processEvents();
  90. require(hmi_editor->transform().m11() > compact_scale,
  91. "HMI page must refit when the window becomes larger");
  92. require(editing_action->isChecked(), "editing action must be selected initially");
  93. require(project_dock->isEnabled(), "project dock must be enabled while editing");
  94. require(properties_dock->isEnabled(), "properties dock must be enabled while editing");
  95. add_button_action->trigger();
  96. const std::string page_id = editor_service.firstPageId();
  97. require(editor_service.findPage(page_id)->controls.size() == 1,
  98. "adding a control must update the HMI page model");
  99. require(selection->text() == QStringLiteral("button-1(未绑定)"),
  100. "an unbound added control must be marked in the property panel");
  101. require(text_edit->text() == QStringLiteral("按钮"),
  102. "property panel must show the control text");
  103. delete_control_action->trigger();
  104. require(editor_service.findPage(page_id)->controls.empty(),
  105. "deleting a selected control must update the HMI page model");
  106. add_normally_open_action->trigger();
  107. parallel_insert_action->trigger();
  108. add_normally_open_action->trigger();
  109. add_normal_coil_action->trigger();
  110. const ControlLogic *logic = logic_editor_service.findLogic(
  111. logic_editor_service.firstLogicId());
  112. require(logic != nullptr && logic->rungs.size() == 1,
  113. "logic actions must edit the default ladder rung");
  114. require(logic->rungs.front().stages.size() == 1
  115. && logic->rungs.front().stages.front().branches.size() == 2,
  116. "parallel insert mode must add a branch to the selected stage");
  117. require(logic->rungs.front().output.has_value(),
  118. "coil action must set the fixed ladder output");
  119. offline_action->trigger();
  120. require(mode_service.mode() == ApplicationMode::Editing,
  121. "unconfigured ladder nodes must block offline running");
  122. const LadderRung &rung = logic->rungs.front();
  123. for (const LogicNode &node : rung.stages.front().branches)
  124. {
  125. require(logic_editor_service.updateNodeConfig(
  126. logic_editor_service.firstLogicId(),
  127. node.id,
  128. ContactNodeConfig{
  129. RegisterAddress{RegisterArea::M, 0},
  130. ContactMode::NormallyOpen})
  131. .succeeded,
  132. "applying a contact configuration must complete the ladder node");
  133. }
  134. require(logic_editor_service.updateNodeConfig(
  135. logic_editor_service.firstLogicId(),
  136. rung.output->id,
  137. CoilNodeConfig{
  138. RegisterAddress{RegisterArea::M, 1},
  139. CoilMode::Normal})
  140. .succeeded,
  141. "applying a coil configuration must complete the ladder node");
  142. offline_action->trigger();
  143. require(mode_service.mode() == ApplicationMode::OfflineRunning,
  144. "offline action must enter offline running");
  145. require(simulation_service.state() == SimulationState::Running,
  146. "offline action must start the actual simulation service");
  147. require(executor_status->text().contains(QStringLiteral("运行")),
  148. "executor status label must report the actual running state");
  149. require(!project_dock->isEnabled(),
  150. "project dock must be disabled while running");
  151. require(!properties_dock->isEnabled(),
  152. "properties dock must be disabled while running");
  153. require(!add_button_action->isEnabled(),
  154. "HMI add controls must be disabled while running");
  155. require(!add_normally_open_action->isEnabled(),
  156. "logic add nodes must be disabled while running");
  157. online_action->trigger();
  158. require(mode_service.mode() == ApplicationMode::OfflineRunning,
  159. "running modes must not switch directly through the UI");
  160. require(offline_action->isChecked(),
  161. "failed mode changes must restore the active action");
  162. editing_action->trigger();
  163. require(mode_service.mode() == ApplicationMode::Editing,
  164. "editing action must return to editing");
  165. require(simulation_service.state() == SimulationState::Stopped,
  166. "editing action must stop the simulation service");
  167. require(executor_status->text() == QStringLiteral("逻辑执行器:停止"),
  168. "executor status label must report the actual stopped state");
  169. require(project_dock->isEnabled(),
  170. "project dock must be restored after returning to editing");
  171. require(properties_dock->isEnabled(),
  172. "properties dock must be restored after returning to editing");
  173. require(add_button_action->isEnabled(),
  174. "HMI add controls must be restored after returning to editing");
  175. require(add_normally_open_action->isEnabled(),
  176. "logic add nodes must be restored after returning to editing");
  177. online_action->trigger();
  178. require(mode_service.mode() == ApplicationMode::Editing,
  179. "online action must require an initial PLC read");
  180. require(editing_action->isChecked(),
  181. "rejected online running must restore the editing action");
  182. }
  183. } // namespace
  184. int main(int argc, char *argv[])
  185. {
  186. // 无窗口平台使 Qt Widgets 测试可在自动化环境稳定运行
  187. qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("offscreen"));
  188. QApplication application(argc, argv);
  189. try
  190. {
  191. testModeActionsControlEditingAvailability();
  192. }
  193. catch (const std::exception &error)
  194. {
  195. std::cerr << "main window tests failed: " << error.what() << '\n';
  196. return 1;
  197. }
  198. std::cout << "main window tests passed\n";
  199. return 0;
  200. }