Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Now it has been fixed in Sirius Web 2025.8.4, `SysONUpdateLibraryExecutor` has b
- https://github.com/eclipse-syson/syson/issues/1547[#1547] [diagrams] Fix an issue where the _Delete from diagram_ action did nothing on elements inside a _Package_ in diagrams.
- https://github.com/eclipse-syson/syson/issues/1567[#1567] [diagrams] Fix an issue where a `PartUsage` contained in a `PartUsage` contained in a `Package` was not displayed, even though it was exposed in the `ViewUsage` corresponding to the diagram.
`PartUsage` is just one example, but the same issue could appear with any other `Element` contained in `Element` contained in a `Package`.
- https://github.com/eclipse-syson/syson/issues/1579[#1579] [diagrams] Fix an issue where the _action flow_ compartment was not revealed when the _New Decision_/_New Fork_/_New Join_/_New Merge_ tools were executed.

=== Improvements

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
/*******************************************************************************
* Copyright (c) 2025 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 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.Node;
import org.eclipse.sirius.components.diagrams.ViewModifier;
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.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;

/**
* Tests the control nodes (fork, merge, decision, join) inside the General View diagram.
*
* @author arichard
*/
@Transactional
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class GVControlNodeTests extends AbstractIntegrationTests {

private static final String ACTION_LABEL = "\u00ABref action\u00BB\naction";

private static final String ACTION_FLOW_LABEL = "action flow";

@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<DiagramRefreshedEventPayload> 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, WHEN a DecisionNode is created, THEN the diagram shows the action flow compartment and the DecisionNode inside the ActionUsage")
@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 createDecisionNode() {
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 newDecisionNodeToolId = diagramDescriptionIdProvider.getNodeCreationToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getActionUsage()), "New Decision");
assertThat(newDecisionNodeToolId).as("The tool 'New Decision' should exist on ActionUsage").isNotNull();

var diagramId = new AtomicReference<String>();
var actionNodeId = new AtomicReference<String>();

Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
diagramId.set(diagram.getId());

var actionNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).getNode();
assertThat(actionNode).isNotNull();
actionNodeId.set(actionNode.getId());

var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Hidden);
});

Runnable newDecisionTool = () -> this.toolTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagramId.get(), actionNodeId.get(), newDecisionNodeToolId,
List.of());

Consumer<Object> updatedDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Normal);
List<Node> childNodes = actionFlowCompartmentNode.getChildNodes();
assertThat(childNodes).hasSize(1);
assertThat(childNodes.get(0).getOutsideLabels()).isNotNull();
assertThat(childNodes.get(0).getOutsideLabels().get(0).text()).isEqualTo("decisionNode1");
});

StepVerifier.create(flux)
.consumeNextWith(initialDiagramContentConsumer)
.then(newDecisionTool)
.consumeNextWith(updatedDiagramContentConsumer)
.thenCancel()
.verify(Duration.ofSeconds(10));
}

@DisplayName("GIVEN a diagram with an ActionUsage, WHEN a ForkNode is created, THEN the diagram shows the action flow compartment and the ForkNode inside the ActionUsage")
@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 createForkNode() {
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 newForkNodeToolId = diagramDescriptionIdProvider.getNodeCreationToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getActionUsage()), "New Fork");
assertThat(newForkNodeToolId).as("The tool 'New Fork' should exist on ActionUsage").isNotNull();

var diagramId = new AtomicReference<String>();
var actionNodeId = new AtomicReference<String>();

Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
diagramId.set(diagram.getId());

var actionNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).getNode();
assertThat(actionNode).isNotNull();
actionNodeId.set(actionNode.getId());

var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Hidden);
});

Runnable newForkTool = () -> this.toolTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagramId.get(), actionNodeId.get(), newForkNodeToolId,
List.of());

Consumer<Object> updatedDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Normal);
List<Node> childNodes = actionFlowCompartmentNode.getChildNodes();
assertThat(childNodes).hasSize(1);
assertThat(childNodes.get(0).getOutsideLabels()).isNotNull();
assertThat(childNodes.get(0).getOutsideLabels().get(0).text()).isEqualTo("forkNode1");
});

StepVerifier.create(flux)
.consumeNextWith(initialDiagramContentConsumer)
.then(newForkTool)
.consumeNextWith(updatedDiagramContentConsumer)
.thenCancel()
.verify(Duration.ofSeconds(10));
}

@DisplayName("GIVEN a diagram with an ActionUsage, WHEN a JoinNode is created, THEN the diagram shows the action flow compartment and the JoinNode inside the ActionUsage")
@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 createJoinNode() {
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 newJoinNodeToolId = diagramDescriptionIdProvider.getNodeCreationToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getActionUsage()), "New Join");
assertThat(newJoinNodeToolId).as("The tool 'New Join' should exist on ActionUsage").isNotNull();

var diagramId = new AtomicReference<String>();
var actionNodeId = new AtomicReference<String>();

Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
diagramId.set(diagram.getId());

var actionNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).getNode();
assertThat(actionNode).isNotNull();
actionNodeId.set(actionNode.getId());

var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Hidden);
});

Runnable newJoinTool = () -> this.toolTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagramId.get(), actionNodeId.get(), newJoinNodeToolId,
List.of());

Consumer<Object> updatedDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Normal);
List<Node> childNodes = actionFlowCompartmentNode.getChildNodes();
assertThat(childNodes).hasSize(1);
assertThat(childNodes.get(0).getOutsideLabels()).isNotNull();
assertThat(childNodes.get(0).getOutsideLabels().get(0).text()).isEqualTo("joinNode1");
});

StepVerifier.create(flux)
.consumeNextWith(initialDiagramContentConsumer)
.then(newJoinTool)
.consumeNextWith(updatedDiagramContentConsumer)
.thenCancel()
.verify(Duration.ofSeconds(10));
}

@DisplayName("GIVEN a diagram with an ActionUsage, WHEN a MergeNode is created, THEN the diagram shows the action flow compartment and the MergeNode inside the ActionUsage")
@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 createMergeNode() {
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 newMergeNodeToolId = diagramDescriptionIdProvider.getNodeCreationToolId(this.descriptionNameGenerator.getNodeName(SysmlPackage.eINSTANCE.getActionUsage()), "New Merge");
assertThat(newMergeNodeToolId).as("The tool 'New Merge' should exist on ActionUsage").isNotNull();

var diagramId = new AtomicReference<String>();
var actionNodeId = new AtomicReference<String>();

Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
diagramId.set(diagram.getId());

var actionNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).getNode();
assertThat(actionNode).isNotNull();
actionNodeId.set(actionNode.getId());

var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Hidden);
});

Runnable newMergeTool = () -> this.toolTester.invokeTool(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, diagramId.get(), actionNodeId.get(), newMergeNodeToolId,
List.of());

Consumer<Object> updatedDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> {
var actionFlowCompartmentNode = new DiagramNavigator(diagram).nodeWithLabel(ACTION_LABEL).childNodeWithLabel(ACTION_FLOW_LABEL).getNode();
assertThat(actionFlowCompartmentNode.getState()).isEqualTo(ViewModifier.Normal);
List<Node> childNodes = actionFlowCompartmentNode.getChildNodes();
assertThat(childNodes).hasSize(1);
assertThat(childNodes.get(0).getOutsideLabels()).isNotNull();
assertThat(childNodes.get(0).getOutsideLabels().get(0).text()).isEqualTo("mergeNode1");
});

StepVerifier.create(flux)
.consumeNextWith(initialDiagramContentConsumer)
.then(newMergeTool)
.consumeNextWith(updatedDiagramContentConsumer)
.thenCancel()
.verify(Duration.ofSeconds(10));
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.eclipse.sirius.components.view.diagram.SynchronizationPolicy;
import org.eclipse.sirius.components.view.diagram.UserResizableDirection;
import org.eclipse.syson.diagram.common.view.services.ViewEdgeToolSwitch;
import org.eclipse.syson.util.AQLConstants;
import org.eclipse.syson.util.AQLUtils;
import org.eclipse.syson.util.IDescriptionNameGenerator;
import org.eclipse.syson.util.SysMLMetamodelHelper;
Expand Down Expand Up @@ -120,7 +121,7 @@ private OutsideLabelStyle createOutsideLabelStyle() {
return this.diagramBuilderHelper.newOutsideLabelStyle()
.borderSize(0)
.labelColor(this.colorProvider.getColor(ViewConstants.DEFAULT_LABEL_COLOR))
.showIconExpression("aql:false")
.showIconExpression(AQLConstants.AQL_FALSE)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
import org.eclipse.emf.ecore.EObject;
import org.eclipse.syson.sysml.ActionUsage;
import org.eclipse.syson.sysml.ConstraintUsage;
import org.eclipse.syson.sysml.DecisionNode;
import org.eclipse.syson.sysml.ForkNode;
import org.eclipse.syson.sysml.JoinNode;
import org.eclipse.syson.sysml.MergeNode;
import org.eclipse.syson.sysml.OccurrenceDefinition;
import org.eclipse.syson.sysml.OccurrenceUsage;
import org.eclipse.syson.sysml.StateUsage;
Expand Down Expand Up @@ -53,6 +57,26 @@ public Boolean caseConstraintUsage(ConstraintUsage object) {
return !this.isGeneralView;
}

@Override
public Boolean caseDecisionNode(DecisionNode object) {
return Boolean.TRUE;
}

@Override
public Boolean caseForkNode(ForkNode object) {
return Boolean.TRUE;
}

@Override
public Boolean caseJoinNode(JoinNode object) {
return Boolean.TRUE;
}

@Override
public Boolean caseMergeNode(MergeNode object) {
return Boolean.TRUE;
}

@Override
public Boolean caseOccurrenceDefinition(OccurrenceDefinition object) {
return !this.isGeneralView;
Expand Down
Loading
Loading