diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 352493867..c8885934a 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -56,6 +56,7 @@ This fix ensure that imported models containing, for example, a top-level `Libra - https://github.com/eclipse-syson/syson/issues/1865[#1865] [explorer] Fix focus loss on some characters in the _New object from text_ modal. - https://github.com/eclipse-syson/syson/issues/1876[#1876] [explorer] Fix potential NPE when trying to delete elements with associated `EAnnotations` from the _Explorer_. - https://github.com/eclipse-syson/syson/issues/1858[#1858] [diagrams] Fix an issue where a new graphical node could be located inside the wrong graphical container. +- https://github.com/eclipse-syson/syson/issues/1859[#1859] [diagrams] Fix an issue where the creation of an `ItemUsage` from an `ActionUsage` or an `ActionDefinition` graphical node was not revealing it. === Improvements diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVItemUsageTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVItemUsageTests.java new file mode 100644 index 000000000..87d14da39 --- /dev/null +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVItemUsageTests.java @@ -0,0 +1,185 @@ +/******************************************************************************* + * Copyright (c) 2026 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.syson.application.controllers.diagrams.general.view; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.sirius.components.diagrams.tests.DiagramEventPayloadConsumer.assertRefreshedDiagramThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.Duration; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput; +import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload; +import org.eclipse.sirius.components.diagrams.tests.navigation.DiagramNavigator; +import org.eclipse.sirius.components.view.emf.diagram.IDiagramIdProvider; +import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; +import org.eclipse.syson.AbstractIntegrationTests; +import org.eclipse.syson.application.controllers.diagrams.testers.ToolTester; +import org.eclipse.syson.application.data.GeneralViewWithTopNodesTestProjectData; +import org.eclipse.syson.services.diagrams.DiagramDescriptionIdProvider; +import org.eclipse.syson.services.diagrams.api.IGivenDiagramDescription; +import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription; +import org.eclipse.syson.standard.diagrams.view.SDVDescriptionNameGenerator; +import org.eclipse.syson.sysml.SysmlPackage; +import org.eclipse.syson.sysml.helper.LabelConstants; +import org.eclipse.syson.util.IDescriptionNameGenerator; +import org.eclipse.syson.util.SysONRepresentationDescriptionIdentifiers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.SqlConfig; +import org.springframework.transaction.annotation.Transactional; + +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +/** + * ItemUsage related-tests in the General View diagram. + * + * @author arichard + */ +@Transactional +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class GVItemUsageTests extends AbstractIntegrationTests { + + private static final String PACKAGE = "Package"; + + private static final String PACKAGE1 = "Package1"; + + @Autowired + private IGivenInitialServerState givenInitialServerState; + + @Autowired + private IGivenDiagramSubscription givenDiagramSubscription; + + @Autowired + private IGivenDiagramDescription givenDiagramDescription; + + @Autowired + private IDiagramIdProvider diagramIdProvider; + + @Autowired + private ToolTester toolTester; + + private final IDescriptionNameGenerator descriptionNameGenerator = new SDVDescriptionNameGenerator(); + + private Flux givenSubscriptionToDiagram() { + var diagramEventInput = new DiagramEventInput(UUID.randomUUID(), + GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + GeneralViewWithTopNodesTestProjectData.GraphicalIds.DIAGRAM_ID); + var flux = this.givenDiagramSubscription.subscribe(diagramEventInput); + return flux; + } + + @BeforeEach + public void beforeEach() { + this.givenInitialServerState.initialize(); + } + + @DisplayName("GIVEN a diagram with an ActionUsage node, WHEN an ItemUsage is created from this node, THEN the new ItemUsage is visible as sibling node") + @Sql(scripts = { GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, + config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + @Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + @Test + public void testCreateItemOnActionUsage() { + var flux = this.givenSubscriptionToDiagram(); + + var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); + var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); + + var newItemUsageOnActionUsageToolId = diagramDescriptionIdProvider.getNodeToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getActionUsage()), "New Item"); + assertThat(newItemUsageOnActionUsageToolId).as("The tool 'New Item' should exist on the ActionUsage").isNotNull(); + + var diagramId = new AtomicReference(); + var actionUsageNodeId = new AtomicReference(); + + Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diag -> { + diagramId.set(diag.getId()); + var actionUsageNode = new DiagramNavigator(diag) + .nodeWithLabel(LabelConstants.OPEN_QUOTE + "ref action" + LabelConstants.CLOSE_QUOTE + LabelConstants.CR + "action").getNode(); + actionUsageNodeId.set(actionUsageNode.getId()); + }); + + Runnable newItemUsageOnActionUsage = () -> this.toolTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagramId.get(), actionUsageNodeId.get(), + newItemUsageOnActionUsageToolId, List.of()); + + Consumer updatedDiagramContentConsumerAfterNewItemOnAction = assertRefreshedDiagramThat(diag -> { + var itemNodeUsage = new DiagramNavigator(diag) + .nodeWithLabel(LabelConstants.OPEN_QUOTE + "item" + LabelConstants.CLOSE_QUOTE + LabelConstants.CR + "item1").getNode(); + assertThat(itemNodeUsage).isNotNull(); + + var newEdgeTargetNode = new DiagramNavigator(diag).edgeWithTargetObjectId(GeneralViewWithTopNodesTestProjectData.SemanticIds.ACTION_USAGE_ID).targetNode().getNode(); + assertEquals(newEdgeTargetNode, itemNodeUsage); + }); + + StepVerifier.create(flux) + .consumeNextWith(initialDiagramContentConsumer) + .then(newItemUsageOnActionUsage) + .consumeNextWith(updatedDiagramContentConsumerAfterNewItemOnAction) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + + @DisplayName("GIVEN a diagram with an ActionDefinition node, WHEN an ItemUsage is created from this node, THEN the new ItemUsage is visible as sibling node") + @Sql(scripts = { GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, + config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + @Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + @Test + public void testCreateItemOnActionDefinition() { + var flux = this.givenSubscriptionToDiagram(); + + var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); + var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); + + var newItemUsageOnActionDefinitionToolId = diagramDescriptionIdProvider.getNodeToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getActionDefinition()), "New Item"); + assertThat(newItemUsageOnActionDefinitionToolId).as("The tool 'New Item' should exist on the ActionDefinition").isNotNull(); + + var diagramId = new AtomicReference(); + var actionDefNodeId = new AtomicReference(); + + Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diag -> { + diagramId.set(diag.getId()); + var actionDefNode = new DiagramNavigator(diag) + .nodeWithLabel(LabelConstants.OPEN_QUOTE + "action def" + LabelConstants.CLOSE_QUOTE + LabelConstants.CR + "ActionDefinition :> UseCaseDefinition").getNode(); + actionDefNodeId.set(actionDefNode.getId()); + }); + + Runnable newItemUsageOnActionDefinition = () -> this.toolTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagramId.get(), actionDefNodeId.get(), + newItemUsageOnActionDefinitionToolId, List.of()); + + Consumer updatedDiagramContentConsumerAfterNewItemOnActionDef = assertRefreshedDiagramThat(diag -> { + var itemNodeUsage = new DiagramNavigator(diag) + .nodeWithLabel(LabelConstants.OPEN_QUOTE + "item" + LabelConstants.CLOSE_QUOTE + LabelConstants.CR + "item1").getNode(); + assertThat(itemNodeUsage).isNotNull(); + + var newEdgeTargetNode = new DiagramNavigator(diag).edgeWithTargetObjectId(GeneralViewWithTopNodesTestProjectData.SemanticIds.ACTION_DEFINITION_ID).targetNode().getNode(); + assertEquals(newEdgeTargetNode, itemNodeUsage); + }); + + StepVerifier.create(flux) + .consumeNextWith(initialDiagramContentConsumer) + .then(newItemUsageOnActionDefinition) + .consumeNextWith(updatedDiagramContentConsumerAfterNewItemOnActionDef) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } +} diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVPortUsageTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVPortUsageTests.java index 3bc9650eb..a1d9d45d9 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVPortUsageTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVPortUsageTests.java @@ -177,6 +177,6 @@ public void testCreatePortInSubPackage() { .then(newPortUsageTool) .consumeNextWith(updatedDiagramContentConsumerAfterNewPortUsage) .thenCancel() - .verify(Duration.ofSeconds(100)); + .verify(Duration.ofSeconds(10)); } } diff --git a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationExposeService.java b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationExposeService.java index f97d74972..614a08b0e 100644 --- a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationExposeService.java +++ b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationExposeService.java @@ -414,7 +414,7 @@ private void hideNodeIfBorderNodeCouldHostTheFutureNode(Element element, IEditin private void hideNodeIfNestedIsDefault(Element element, IEditingContext editingContext, DiagramContext diagramContext, Node selectedNode, Map convertedNodes) { - if (element instanceof AttributeUsage || element instanceof PortUsage || Objects.equals(SysmlPackage.eINSTANCE.getItemUsage(), element.eClass())) { + if (element instanceof AttributeUsage || element instanceof PortUsage || this.isItemNotContainedByAction(element)) { boolean compartmentOrBorderThatCouldHostTheFutureNode = false; for (Node childNode : selectedNode.getChildNodes()) { Optional childNodeDescriptionIdForRendering = this.diagramMutationElementService.getChildNodeDescriptionIdForRendering(element, editingContext, diagramContext, childNode, @@ -437,4 +437,17 @@ private void hideNodeIfNestedIsDefault(Element element, IEditingContext editingC } } } + + private boolean isItemNotContainedByAction(Element element) { + boolean isItemNotContainedByAction = false; + if (Objects.equals(SysmlPackage.eINSTANCE.getItemUsage(), element.eClass())) { + var owner = element.getOwner(); + if (owner instanceof ActionUsage || owner instanceof ActionDefinition) { + isItemNotContainedByAction = false; + } else { + isItemNotContainedByAction = true; + } + } + return isItemNotContainedByAction; + } } diff --git a/doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc index 3eee60311..4c6e3766c 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc @@ -52,6 +52,7 @@ The following structure is not yet handled `connection bus : C connect (d1, d2, ** Fix an issue where the creation of an `Element` inside a `sub-Package` created two graphical nodes instead of one. ** Fix an issue where the creation of an `Element` inside a `sub-Package` could hide unrelated graphical nodes. ** Fix an issue where a new graphical node could be located inside the wrong graphical container. +** Fix an issue where the creation of an `ItemUsage` from an `ActionUsage` or an `ActionDefinition` graphical node was not revealing it. * In _Explorer_ view: