From 2468cc6d367676b023e8c6688d238597d8dd16d8 Mon Sep 17 00:00:00 2001 From: suyu <1643689728@qq.com> Date: Wed, 19 Aug 2026 14:12:51 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=B9=B6=E8=81=94?= =?UTF-8?q?=E6=94=AF=E8=B7=AF=E6=89=B9=E9=87=8F=E5=88=A0=E9=99=A4=E6=AE=8B?= =?UTF-8?q?=E7=95=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/domain/control_logic_model.cpp | 6 ++ app/src/services/logic_editor_service.cpp | 9 +++ app/tests/logic_editor_service_tests.cpp | 55 +++++++++++++++ app/tests/main_window_tests.cpp | 82 +++++++++++++++++++++++ 4 files changed, 152 insertions(+) diff --git a/app/src/domain/control_logic_model.cpp b/app/src/domain/control_logic_model.cpp index cfcd19c..854a3d2 100644 --- a/app/src/domain/control_logic_model.cpp +++ b/app/src/domain/control_logic_model.cpp @@ -251,6 +251,12 @@ void normalizeExpression(ConditionExpression *expression) std::vector flattened; for (ConditionExpression &child : expression->children) { + if ((child.kind == ConditionExpressionKind::Series + || child.kind == ConditionExpressionKind::Parallel) + && child.children.empty()) + { + continue; + } if (child.kind == expression->kind) { for (ConditionExpression &grandchild : child.children) diff --git a/app/src/services/logic_editor_service.cpp b/app/src/services/logic_editor_service.cpp index 0608707..d880f5c 100644 --- a/app/src/services/logic_editor_service.cpp +++ b/app/src/services/logic_editor_service.cpp @@ -1430,6 +1430,15 @@ LogicEditorResult LogicEditorService::removeNodes( } } } + for (const LadderRung &rung : editable_logic->rungs) + { + std::string validation_error; + if (!rung.validate(&validation_error)) + { + project.controlLogics = std::move(before.logics); + return failure(LogicEditorError::InvalidOperation, validation_error); + } + } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_ids.front()}; } diff --git a/app/tests/logic_editor_service_tests.cpp b/app/tests/logic_editor_service_tests.cpp index b118380..5519223 100644 --- a/app/tests/logic_editor_service_tests.cpp +++ b/app/tests/logic_editor_service_tests.cpp @@ -192,6 +192,60 @@ void testStructuredWireEditing() "wire branch deletion must participate in ladder undo history"); } +void testBatchDeleteAllNodesInParallelBranch() +{ + TestProjectStorage storage; + ProjectService project_service(storage); + LogicEditorService service(project_service); + const std::string logic_id = service.ensureDefaultLogic().id; + const std::string rung_id = service.firstRungId(logic_id); + + const LogicEditorResult first = service.appendCondition( + logic_id, rung_id, contact(0)); + const LogicEditorResult second = service.appendCondition( + logic_id, rung_id, contact(1)); + const LogicEditorResult third = service.appendCondition( + logic_id, rung_id, contact(2)); + const LogicEditorResult fourth = service.appendCondition( + logic_id, rung_id, contact(3)); + require(first.succeeded && second.succeeded && third.succeeded + && fourth.succeeded, + "parallel batch deletion setup contacts must be created"); + + const LogicEditorResult branch = service.addParallelBranch( + logic_id, rung_id, + {second.id, third.id, fourth.id}, + contact(10)); + require(branch.succeeded, + "parallel batch deletion setup branch must be created"); + + require(service.removeNodes( + logic_id, {second.id, third.id, fourth.id}) + .succeeded, + "deleting every node in a parallel branch as one batch must succeed"); + const LadderRung *rung = service.findRung(logic_id, rung_id); + require(rung != nullptr && rung->condition.has_value() + && rung->validate(), + "batch deletion must leave a valid normalized ladder expression"); + require(rung->condition->kind == ConditionExpressionKind::Series + && rung->condition->children.size() == 2U + && rung->condition->children.at(0).kind + == ConditionExpressionKind::Node + && rung->condition->children.at(1).kind + == ConditionExpressionKind::Node, + "an empty parallel branch must collapse into the remaining branch"); + require(service.findNode(logic_id, second.id) == nullptr + && service.findNode(logic_id, third.id) == nullptr + && service.findNode(logic_id, fourth.id) == nullptr, + "all selected parallel branch nodes must be removed"); + require(service.undo().succeeded, + "parallel batch deletion must be undoable"); + require(service.findNode(logic_id, second.id) != nullptr + && service.findNode(logic_id, third.id) != nullptr + && service.findNode(logic_id, fourth.id) != nullptr, + "undo must restore every deleted parallel branch node"); +} + void testConditionColumnLimit() { TestProjectStorage storage; @@ -415,6 +469,7 @@ int main() testStructuredEditingAndNormalization(); testRangeParallelInsertion(); testStructuredWireEditing(); + testBatchDeleteAllNodesInParallelBranch(); testConditionColumnLimit(); testUnconditionalOutputEditing(); testLogicLifecycleAndOrdering(); diff --git a/app/tests/main_window_tests.cpp b/app/tests/main_window_tests.cpp index 7c79a8e..2d46a75 100644 --- a/app/tests/main_window_tests.cpp +++ b/app/tests/main_window_tests.cpp @@ -43,9 +43,11 @@ #include #include +#include #include #include #include +#include namespace { @@ -285,6 +287,85 @@ QColor renderedColorAt(HmiEditorWidget &view, const QPoint &viewport_position) return image.pixelColor(viewport_position); } +ContactNodeConfig logicContact(int address) +{ + return {RegisterAddress{RegisterArea::M, address}, ContactMode::NormallyOpen}; +} + +void testLogicEditorBatchDeletesWholeParallelRow() +{ + TestProjectStorage storage; + ProjectService project_service(storage); + LogicEditorService service(project_service); + const std::string logic_id = service.ensureDefaultLogic().id; + const std::string rung_id = service.firstRungId(logic_id); + + std::vector top_ids; + for (int address = 0; address < 4; ++address) + { + const LogicEditorResult result = service.appendCondition( + logic_id, rung_id, logicContact(address)); + require(result.succeeded, + "logic UI batch deletion setup contacts must be created"); + top_ids.push_back(result.id); + } + const LogicEditorResult lower_first = service.addParallelBranch( + logic_id, rung_id, top_ids, logicContact(10)); + require(lower_first.succeeded, + "logic UI batch deletion setup branch must be created"); + std::string lower_tail = lower_first.id; + for (int address = 11; address < 14; ++address) + { + const LogicEditorResult result = service.insertConditionAfter( + logic_id, rung_id, lower_tail, logicContact(address)); + require(result.succeeded, + "logic UI batch deletion lower branch contacts must be created"); + lower_tail = result.id; + } + + LogicEditorWidget view(service); + view.setLogicId(logic_id); + qreal lower_row_y = 0.0; + for (QGraphicsItem *item : view.scene()->items()) + { + if (qFuzzyCompare(item->zValue(), 3.0)) + { + lower_row_y = std::max(lower_row_y, item->scenePos().y()); + } + } + int selected_count = 0; + for (QGraphicsItem *item : view.scene()->items()) + { + if (qFuzzyCompare(item->zValue(), 3.0) + && qFuzzyCompare(item->scenePos().y(), lower_row_y)) + { + item->setSelected(true); + ++selected_count; + } + } + require(selected_count == 4, + "logic UI batch deletion must select every contact in the lower branch"); + require(view.deleteSelected().succeeded, + "logic UI must batch delete a complete parallel row"); + + const LadderRung *rung = service.findRung(logic_id, rung_id); + require(rung != nullptr && rung->condition.has_value() && rung->validate(), + "logic UI batch deletion must leave a valid ladder expression"); + require(rung->condition->kind == ConditionExpressionKind::Series + && rung->condition->children.size() == 4U, + "logic UI batch deletion must preserve only the original series row"); + const QList rendered_items = view.scene()->items(); + const bool has_vertical_connector = std::any_of( + rendered_items.cbegin(), + rendered_items.cend(), + [](const QGraphicsItem *item) + { + return qFuzzyCompare(item->zValue(), 2.5); + }); + require(!has_vertical_connector, + "logic UI must not render a vertical connector after removing the branch"); +} + void testRuntimeButtonMouseInteraction() { TestProjectStorage storage; @@ -1645,6 +1726,7 @@ int main(int argc, char *argv[]) try { + testLogicEditorBatchDeletesWholeParallelRow(); testRuntimeButtonMouseInteraction(); testRuntimeProgressBarRendering(); testRuntimePageJumpDoesNotRequireRegisterWritePermission();