diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index b94d65b30..fa4d2c451 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -32,6 +32,7 @@ As a result, the following GraphQL mutations have been removed `exposeRequiremen === Improvements +- https://github.com/eclipse-syson/syson/issues/2108[#2108] [diagrams] Leverage the latest change of the selection dialog to allow creating an `Actor` graphical node without specialization. === New features diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controller/editingcontext/checkers/SemanticCheckerService.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controller/editingcontext/checkers/SemanticCheckerService.java index 4e110aa1e..c9799be6d 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controller/editingcontext/checkers/SemanticCheckerService.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controller/editingcontext/checkers/SemanticCheckerService.java @@ -53,6 +53,20 @@ public ISemanticChecker getElementInParentSemanticChecker(String parentLabel, ER return this.getElementInParentSemanticChecker(parentLabel, containmentReference, childEClass, null); } + /** + * Creates a semantic checker that should be checked in an editing context. + * + * @param parentLabel + * The label of the parent element + * @param containmentReference + * The {@link EReference} that contains the element to check in the parent + * @param childEClass + * The expected child EClass + * @param additionalCheckOnElement + * A consumer to allow additional checking of the element. + * The element can either be an object if the EReference is single valued, or a list of object if the referenced is multivalued. + * @return the created semantic checker + */ public ISemanticChecker getElementInParentSemanticChecker(String parentLabel, EReference containmentReference, EClass childEClass, Consumer additionalCheckOnElement) { return new CheckElementInParent(this.objectSearchService, this.rootElementId) .withParentLabel(parentLabel) @@ -75,7 +89,7 @@ public ISemanticChecker getElementInParentSemanticChecker(String parentLabel, ER * @param semanticChecker * the checks that needs to be run * @deprecated this function will be removed when all the tests will be migrated to follow the same format as Sirius Web. - * Please, use {@link SemanticCheckerService#checkElement(Class type, Supplier idSupplier, Consumer semanticChecker)} instead. + * Please, use {@link SemanticCheckerService#checkElement(Class type, Supplier idSupplier, Consumer semanticChecker)}instead. */ @Deprecated public void checkElement(Step verifier, Class type, Supplier idSupplier, Consumer semanticChecker) { diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeAnalysisCreationTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeAnalysisCreationTests.java index 9fe8cb871..8da8cfbb5 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeAnalysisCreationTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeAnalysisCreationTests.java @@ -13,6 +13,7 @@ package org.eclipse.syson.application.controllers.diagrams.general.view; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.type; import static org.eclipse.sirius.components.diagrams.tests.DiagramEventPayloadConsumer.assertRefreshedDiagramThat; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -58,6 +59,7 @@ import org.eclipse.syson.sysml.Element; import org.eclipse.syson.sysml.PartUsage; import org.eclipse.syson.sysml.ReferenceUsage; +import org.eclipse.syson.sysml.Specialization; import org.eclipse.syson.sysml.Subsetting; import org.eclipse.syson.sysml.SysmlPackage; import org.eclipse.syson.sysml.Type; @@ -65,6 +67,7 @@ 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.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -501,30 +504,48 @@ public void createNewDefinitionSubjectInUseCaseUsage() { this.createSubjectWithFeatureTypingInCaseUsage(SysmlPackage.eINSTANCE.getUseCaseUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.USE_CASE_USAGE_ID, USE_CASE); } + @DisplayName("GIVEN a Case, WHEN creating a new Actor selecting a Part, THEN the Actor subsetted by the Part is created in the Case") @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) @Test public void createNewActorWithSubsettingInCaseUsage() { this.createActorWithSubsettingInCaseUsage(SysmlPackage.eINSTANCE.getCaseUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.CASE_USAGE_ID, CASE); } + @DisplayName("GIVEN a UseCase, WHEN creating a new Actor selecting a Part, THEN the Actor subsetted by the Part is created in the UseCase") @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) @Test public void createNewActorWithSubsettingInUseCaseUsage() { this.createActorWithSubsettingInCaseUsage(SysmlPackage.eINSTANCE.getUseCaseUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.USE_CASE_USAGE_ID, USE_CASE); } + @DisplayName("GIVEN a Case, WHEN creating a new Actor selecting a PartDefinition, THEN the Actor typed by the PartDefinition is created in the Case") @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) @Test public void createNewActorWithFeatureTypingInCaseUsage() { this.createActorWithFeatureTypingInCaseUsage(SysmlPackage.eINSTANCE.getCaseUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.CASE_USAGE_ID, CASE); } + @DisplayName("GIVEN a UseCase, WHEN creating a new Actor selecting a PartDefinition, THEN the Actor typed by the PartDefinition is created in the UseCase") @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) @Test public void createNewActorWithFeatureTypingInUseCaseUsage() { this.createActorWithFeatureTypingInCaseUsage(SysmlPackage.eINSTANCE.getUseCaseUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.USE_CASE_USAGE_ID, USE_CASE); } + @DisplayName("GIVEN a Case, WHEN creating a new Actor without selection, THEN the Actor without specialization is created in the Case") + @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) + @Test + public void createNewActorWithoutSpecializationInCaseUsage() { + this.createActorWithoutSpecializationInCaseUsage(SysmlPackage.eINSTANCE.getCaseUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.CASE_USAGE_ID, CASE); + } + + @DisplayName("GIVEN a UseCase, WHEN creating a new Actor without selection, THEN the Actor without specialization is created in the UseCase") + @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) + @Test + public void createNewActorWithoutSpecializationInUseCaseUsage() { + this.createActorWithoutSpecializationInCaseUsage(SysmlPackage.eINSTANCE.getUseCaseUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.USE_CASE_USAGE_ID, USE_CASE); + } + private void createSubjectWithSubsettingInCaseUsage(EClass caseUsageSubclass, String targetObjectId, String parentLabel) { var flux = this.givenSubscriptionToDiagram(); @@ -672,11 +693,9 @@ private void createActorWithSubsettingInCaseUsage(EClass caseUsageSubclass, Stri EClass childEClass = SysmlPackage.eINSTANCE.getPartUsage(); String creationToolName = "New Actor"; EReference containmentReference = SysmlPackage.eINSTANCE.getCaseUsage_ActorParameter(); - List variables = new ArrayList<>(); - String existingPartId = "2c5fe5a5-18fe-40f4-ab66-a2d91ab7df6a"; - variables.add(new ToolVariable("selectedObject", existingPartId, ToolVariableType.OBJECT_ID)); + String existingPartId = GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_USAGE_ID; - Runnable createNodeRunnable = this.creationTestsService.createNode(diagramDescriptionIdProvider, diagram, caseUsageSubclass, targetObjectId, creationToolName, variables); + Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithSingleSelection(diagramDescriptionIdProvider, diagram, caseUsageSubclass, targetObjectId, creationToolName, existingPartId); Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { var initialDiagram = diagram.get(); @@ -687,28 +706,29 @@ private void createActorWithSubsettingInCaseUsage(EClass caseUsageSubclass, Stri .check(initialDiagram, newDiagram, true); }); - ISemanticChecker semanticChecker = (editingContext) -> { - Object semanticRootObject = this.objectSearchService.getObject(editingContext, GeneralViewWithTopNodesTestProjectData.SemanticIds.PACKAGE_1_ID).orElse(null); - assertThat(semanticRootObject).isInstanceOf(Element.class); - Element semanticRootElement = (Element) semanticRootObject; - Optional optActor = EMFUtils.allContainedObjectOfType(semanticRootElement, PartUsage.class) - .filter(element -> Objects.equals(element.getName(), "actor1")) - .findFirst(); - assertThat(optActor).isPresent(); - var actor = optActor.get(); - EList subjectSubsets = actor.getOwnedSubsetting(); - assertFalse(subjectSubsets.isEmpty()); - assertThat(subjectSubsets.get(0).getSubsettedFeature().getName()).isEqualTo("part"); + Consumer additionalCheck = referencedObject -> { + assertThat(referencedObject).isInstanceOf(List.class) + .asInstanceOf(type(List.class)) + .satisfies(actors -> { + assertThat(actors).size().isEqualTo(1); + assertThat(actors.getFirst()) + .isInstanceOf(PartUsage.class) + .asInstanceOf(type(PartUsage.class)) + .satisfies(actor -> { + EList subjectSubsets = actor.getOwnedSubsetting(); + assertThat(subjectSubsets).isNotEmpty(); + assertThat(subjectSubsets.get(0).getSubsettedFeature().getName()).isEqualTo("part"); + }); + }); }; - Runnable semanticCheck = this.semanticCheckerService.checkEditingContext(this.semanticCheckerService.getElementInParentSemanticChecker(parentLabel, containmentReference, childEClass)); - Runnable semanticCheck2 = this.semanticCheckerService.checkEditingContext(semanticChecker); + + Runnable semanticCheck = this.semanticCheckerService.checkEditingContext(this.semanticCheckerService.getElementInParentSemanticChecker(parentLabel, containmentReference, childEClass, additionalCheck)); StepVerifier.create(flux) .consumeNextWith(initialDiagramContentConsumer) .then(createNodeRunnable) .consumeNextWith(diagramCheck) .then(semanticCheck) - .then(semanticCheck2) .thenCancel() .verify(Duration.ofSeconds(10)); } @@ -726,11 +746,9 @@ private void createActorWithFeatureTypingInCaseUsage(EClass caseUsageSubclass, S EClass childEClass = SysmlPackage.eINSTANCE.getPartUsage(); String creationToolName = "New Actor"; EReference containmentReference = SysmlPackage.eINSTANCE.getCaseUsage_ActorParameter(); - List variables = new ArrayList<>(); String existingPartDefId = GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_DEFINITION_ID; - variables.add(new ToolVariable("selectedObject", existingPartDefId, ToolVariableType.OBJECT_ID)); - Runnable createNodeRunnable = this.creationTestsService.createNode(diagramDescriptionIdProvider, diagram, caseUsageSubclass, targetObjectId, creationToolName, variables); + Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithSingleSelection(diagramDescriptionIdProvider, diagram, caseUsageSubclass, targetObjectId, creationToolName, existingPartDefId); Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { var initialDiagram = diagram.get(); @@ -741,28 +759,83 @@ private void createActorWithFeatureTypingInCaseUsage(EClass caseUsageSubclass, S .check(initialDiagram, newDiagram, true); }); - ISemanticChecker semanticChecker = (editingContext) -> { - Object semanticRootObject = this.objectSearchService.getObject(editingContext, GeneralViewWithTopNodesTestProjectData.SemanticIds.PACKAGE_1_ID).orElse(null); - assertThat(semanticRootObject).isInstanceOf(Element.class); - Element semanticRootElement = (Element) semanticRootObject; - Optional optActor = EMFUtils.allContainedObjectOfType(semanticRootElement, PartUsage.class) - .filter(element -> Objects.equals(element.getName(), "actor1")) - .findFirst(); - assertThat(optActor).isPresent(); - var actor = optActor.get(); - EList types = actor.getType(); - assertFalse(types.isEmpty()); - assertThat(types.get(0).getName()).isEqualTo("PartDefinition"); + Consumer additionalCheck = referencedObject -> { + assertThat(referencedObject).isInstanceOf(List.class) + .asInstanceOf(type(List.class)) + .satisfies(actors -> { + assertThat(actors).size().isEqualTo(1); + assertThat(actors.getFirst()) + .isInstanceOf(PartUsage.class) + .asInstanceOf(type(PartUsage.class)) + .satisfies(actor -> { + EList types = actor.getType(); + assertThat(types).isNotEmpty(); + assertThat(types.get(0).getName()).isEqualTo("PartDefinition"); + }); + }); }; - Runnable semanticCheck = this.semanticCheckerService.checkEditingContext(this.semanticCheckerService.getElementInParentSemanticChecker(parentLabel, containmentReference, childEClass)); - Runnable semanticCheck2 = this.semanticCheckerService.checkEditingContext(semanticChecker); + + Runnable semanticCheck = this.semanticCheckerService.checkEditingContext(this.semanticCheckerService.getElementInParentSemanticChecker(parentLabel, containmentReference, childEClass, additionalCheck)); + + StepVerifier.create(flux) + .consumeNextWith(initialDiagramContentConsumer) + .then(createNodeRunnable) + .consumeNextWith(diagramCheck) + .then(semanticCheck) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + + private void createActorWithoutSpecializationInCaseUsage(EClass caseUsageSubclass, String targetObjectId, String parentLabel) { + var flux = this.givenSubscriptionToDiagram(); + + AtomicReference diagram = new AtomicReference<>(); + Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set); + + var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); + var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); + + EClass childEClass = SysmlPackage.eINSTANCE.getPartUsage(); + String creationToolName = "New Actor"; + EReference containmentReference = SysmlPackage.eINSTANCE.getCaseUsage_ActorParameter(); + + Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithoutSelectionProvided(diagramDescriptionIdProvider, diagram, caseUsageSubclass, targetObjectId, creationToolName); + + Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { + var initialDiagram = diagram.get(); + int createdNodesExpectedCount = 1; + new CheckDiagramElementCount(this.diagramComparator) + .hasNewNodeCount(createdNodesExpectedCount) + .hasNewEdgeCount(1) + .check(initialDiagram, newDiagram, true); + }); + + Consumer additionalCheck = referencedObject -> { + assertThat(referencedObject).isInstanceOf(List.class) + .asInstanceOf(type(List.class)) + .satisfies(actors -> { + assertThat(actors).size().isEqualTo(1); + assertThat(actors.getFirst()) + .isInstanceOf(PartUsage.class) + .asInstanceOf(type(PartUsage.class)) + .satisfies(actor -> { + assertThat(actor.getOwnedSpecialization()).allMatch(Specialization::isIsImplied); + EList types = actor.getType(); + assertThat(types) + .isNotEmpty() + .allMatch(Element::isIsLibraryElement); + }); + }); + }; + + Runnable semanticCheck = this.semanticCheckerService.checkEditingContext(this.semanticCheckerService.getElementInParentSemanticChecker(parentLabel, containmentReference, childEClass, additionalCheck)); StepVerifier.create(flux) .consumeNextWith(initialDiagramContentConsumer) .then(createNodeRunnable) .consumeNextWith(diagramCheck) .then(semanticCheck) - .then(semanticCheck2) .thenCancel() .verify(Duration.ofSeconds(10)); } diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeRequirementCreationTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeRequirementCreationTests.java index 2cabb21c7..5e9e938bd 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeRequirementCreationTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeRequirementCreationTests.java @@ -13,6 +13,7 @@ package org.eclipse.syson.application.controllers.diagrams.general.view; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.type; import static org.eclipse.sirius.components.diagrams.tests.DiagramEventPayloadConsumer.assertRefreshedDiagramThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -56,12 +57,15 @@ import org.eclipse.syson.standard.diagrams.view.SDVDescriptionNameGenerator; import org.eclipse.syson.sysml.Element; import org.eclipse.syson.sysml.PartUsage; +import org.eclipse.syson.sysml.Specialization; import org.eclipse.syson.sysml.Subsetting; import org.eclipse.syson.sysml.SysmlPackage; +import org.eclipse.syson.sysml.Type; import org.eclipse.syson.sysml.helper.EMFUtils; 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.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -79,6 +83,7 @@ * @author arichard */ @Transactional +@SuppressWarnings("checkstyle:MultipleStringLiterals") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class GVSubNodeRequirementCreationTests extends AbstractIntegrationTests { @@ -1074,16 +1079,46 @@ public void createNewStakeholderInRequirementUsage() { this.createNewStakeholderIn(SysmlPackage.eINSTANCE.getRequirementUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_USAGE_ID, "requirement"); } + @DisplayName("GIVEN a Requirement, WHEN creating a new Actor selecting a Part, THEN the Actor subsetted by the Part is created in the Requirement") @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) @Test - public void createNewActorInRequirementUsage() { - this.createNewActorIn(SysmlPackage.eINSTANCE.getRequirementUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_USAGE_ID, "requirement"); + public void createNewActorWithSubsettingInRequirementUsage() { + this.createNewActorWithSubsettingIn(SysmlPackage.eINSTANCE.getRequirementUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_USAGE_ID, "requirement"); } + @DisplayName("GIVEN a Requirement, WHEN creating a new Actor selecting a PartDefinition, THEN the Actor typed by the PartDefinition is created in the Requirement") @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) @Test - public void createNewActorInRequirementDefinition() { - this.createNewActorIn(SysmlPackage.eINSTANCE.getRequirementDefinition(), GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_DEFINITION_ID, "RequirementDefinition"); + public void createNewActorWithFeatureTypingInRequirementUsage() { + this.createNewActorWithFeatureTypingIn(SysmlPackage.eINSTANCE.getRequirementUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_USAGE_ID, "requirement"); + } + + @DisplayName("GIVEN a Requirement, WHEN creating a new Actor without selection, THEN the Actor without specialization is created in the Requirement") + @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) + @Test + public void createNewActorWithoutSpecializationInRequirementUsage() { + this.createNewActorWithoutSpecializationIn(SysmlPackage.eINSTANCE.getRequirementUsage(), GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_USAGE_ID, "requirement"); + } + + @DisplayName("GIVEN a RequirementDefinition, WHEN creating a new Actor selecting a Part, THEN the Actor subsetted by the Part is created in the RequirementDefinition") + @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) + @Test + public void createNewActorWithSubsettingInRequirementDefinition() { + this.createNewActorWithSubsettingIn(SysmlPackage.eINSTANCE.getRequirementDefinition(), GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_DEFINITION_ID, "RequirementDefinition"); + } + + @DisplayName("GIVEN a RequirementDefinition, WHEN creating a new Actor selecting a PartDefinition, THEN the Actor typed by the PartDefinition is created in the RequirementDefinition") + @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) + @Test + public void createNewActorWithFeatureTypingInRequirementDefinition() { + this.createNewActorWithFeatureTypingIn(SysmlPackage.eINSTANCE.getRequirementDefinition(), GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_DEFINITION_ID, "RequirementDefinition"); + } + + @DisplayName("GIVEN a RequirementDefinition, WHEN creating a new Actor without selection, THEN the Actor without specialization is created in the RequirementDefinition") + @GivenSysONServer({ GeneralViewWithTopNodesTestProjectData.SCRIPT_PATH }) + @Test + public void createNewActorWithoutSpecializationInRequirementDefinition() { + this.createNewActorWithoutSpecializationIn(SysmlPackage.eINSTANCE.getRequirementDefinition(), GeneralViewWithTopNodesTestProjectData.SemanticIds.REQUIREMENT_DEFINITION_ID, "RequirementDefinition"); } private void createNewStakeholderIn(EClass eClassWithStakeholderParameter, String targetObjectId, String parentNodeLabel) { @@ -1144,7 +1179,7 @@ private void createNewStakeholderIn(EClass eClassWithStakeholderParameter, Strin .verify(Duration.ofSeconds(10)); } - private void createNewActorIn(EClass eClassWithActorParameter, String targetObjectId, String parentNodeLabel) { + private void createNewActorWithSubsettingIn(EClass eClassWithActorParameter, String targetObjectId, String parentNodeLabel) { var flux = this.givenSubscriptionToDiagram(); AtomicReference diagram = new AtomicReference<>(); @@ -1158,11 +1193,11 @@ private void createNewActorIn(EClass eClassWithActorParameter, String targetObje .orElseGet(() -> Assertions.fail("No fitting EReference could be found in '%s'.".formatted(eClassWithActorParameter.getName()))); var actorCreationToolName = "New Actor"; + var existingElementId = GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_USAGE_ID; Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set); - Runnable createNodeRunnable = this.creationTestsService.createNode(diagramDescriptionIdProvider, diagram, eClassWithActorParameter, targetObjectId, actorCreationToolName, - Stream.of(new ToolVariable("selectedObject", /* PartUsage 'part' */ "2c5fe5a5-18fe-40f4-ab66-a2d91ab7df6a", ToolVariableType.OBJECT_ID)).toList()); + Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithSingleSelection(diagramDescriptionIdProvider, diagram, eClassWithActorParameter, targetObjectId, actorCreationToolName, existingElementId); Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { var initialDiagram = diagram.get(); @@ -1178,30 +1213,154 @@ private void createNewActorIn(EClass eClassWithActorParameter, String targetObje .check(initialDiagram, newDiagram); }); - ISemanticChecker semanticChecker = (editingContext) -> { - var semanticRootElement = this.objectSearchService.getObject(editingContext, GeneralViewWithTopNodesTestProjectData.SemanticIds.PACKAGE_1_ID) - .filter(Element.class::isInstance) - .map(Element.class::cast).orElseGet(() -> Assertions.fail("Could not find the expected root semantic object.")); - var allActorsPartUsages = EMFUtils.allContainedObjectOfType(semanticRootElement, PartUsage.class) - .filter(element -> Objects.equals(element.getName(), "actor1")).toList(); - assertEquals(1, allActorsPartUsages.size()); + Consumer additionalCheck = referencedObject -> { + assertThat(referencedObject).isInstanceOf(List.class) + .asInstanceOf(type(List.class)) + .satisfies(actors -> { + assertThat(actors).size().isEqualTo(1); + assertThat(actors.getFirst()) + .isInstanceOf(PartUsage.class) + .asInstanceOf(type(PartUsage.class)) + .satisfies(actorPartUsage -> { + var subsettings = actorPartUsage.getOwnedSubsetting(); + assertThat(subsettings).size().isEqualTo(1); + assertThat(subsettings.get(0).getSubsettedFeature().getName()).isEqualTo("part"); + }); + }); + }; - var actorPartUsage = allActorsPartUsages.get(0); - var subsettings = actorPartUsage.getOwnedSubsetting(); - assertEquals(1, subsettings.size()); - assertThat(subsettings.get(0).getSubsettedFeature().getName()).isEqualTo("part"); + Runnable semanticCheck = this.semanticCheckerService.checkEditingContext( + this.semanticCheckerService.getElementInParentSemanticChecker(parentNodeLabel, actorParameterEReference, SysmlPackage.eINSTANCE.getPartUsage(), additionalCheck)); + + StepVerifier.create(flux) + .consumeNextWith(initialDiagramContentConsumer) + .then(createNodeRunnable) + .consumeNextWith(diagramCheck) + .then(semanticCheck) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + + private void createNewActorWithFeatureTypingIn(EClass eClassWithActorParameter, String targetObjectId, String parentNodeLabel) { + var flux = this.givenSubscriptionToDiagram(); + + AtomicReference diagram = new AtomicReference<>(); + + var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); + var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); + + var actorParameterEReference = eClassWithActorParameter.getEAllReferences().stream() + .filter(eReference -> eReference.getName().equals("actorParameter") && eReference.getEType() == SysmlPackage.eINSTANCE.getPartUsage()).findFirst() + .orElseGet(() -> Assertions.fail("No fitting EReference could be found in '%s'.".formatted(eClassWithActorParameter.getName()))); + + var actorCreationToolName = "New Actor"; + var existingElementId = GeneralViewWithTopNodesTestProjectData.SemanticIds.PART_DEFINITION_ID; + + Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set); + + Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithSingleSelection(diagramDescriptionIdProvider, diagram, eClassWithActorParameter, targetObjectId, actorCreationToolName, existingElementId); + + Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { + var initialDiagram = diagram.get(); + new CheckDiagramElementCount(this.diagramComparator) + .hasNewNodeCount(1) + .hasNewEdgeCount(1) + .check(initialDiagram, newDiagram, true); + new CheckNodeInCompartment(diagramDescriptionIdProvider, this.diagramComparator) + .withTargetObjectId(targetObjectId) + .withCompartmentName("actors") + .hasNodeDescriptionName(this.descriptionNameGenerator.getCompartmentItemName(eClassWithActorParameter, actorParameterEReference)) + .hasCompartmentCount(0) + .check(initialDiagram, newDiagram); + }); + + Consumer additionalCheck = referencedObject -> { + assertThat(referencedObject).isInstanceOf(List.class) + .asInstanceOf(type(List.class)) + .satisfies(actors -> { + assertThat(actors).size().isEqualTo(1); + assertThat(actors.getFirst()) + .isInstanceOf(PartUsage.class) + .asInstanceOf(type(PartUsage.class)) + .satisfies(actorPartUsage -> { + EList types = actorPartUsage.getType(); + assertThat(types).isNotEmpty(); + assertThat(types.get(0).getName()).isEqualTo("PartDefinition"); + }); + }); }; - Runnable semanticCheck1 = this.semanticCheckerService.checkEditingContext( - this.semanticCheckerService.getElementInParentSemanticChecker(parentNodeLabel, actorParameterEReference, SysmlPackage.eINSTANCE.getPartUsage())); - Runnable semanticCheck2 = this.semanticCheckerService.checkEditingContext(semanticChecker); + Runnable semanticCheck = this.semanticCheckerService.checkEditingContext( + this.semanticCheckerService.getElementInParentSemanticChecker(parentNodeLabel, actorParameterEReference, SysmlPackage.eINSTANCE.getPartUsage(), additionalCheck)); StepVerifier.create(flux) .consumeNextWith(initialDiagramContentConsumer) .then(createNodeRunnable) .consumeNextWith(diagramCheck) - .then(semanticCheck1) - .then(semanticCheck2) + .then(semanticCheck) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + + private void createNewActorWithoutSpecializationIn(EClass eClassWithActorParameter, String targetObjectId, String parentNodeLabel) { + var flux = this.givenSubscriptionToDiagram(); + + AtomicReference diagram = new AtomicReference<>(); + + var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewWithTopNodesTestProjectData.EDITING_CONTEXT_ID, + SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); + var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); + + var actorParameterEReference = eClassWithActorParameter.getEAllReferences().stream() + .filter(eReference -> eReference.getName().equals("actorParameter") && eReference.getEType() == SysmlPackage.eINSTANCE.getPartUsage()).findFirst() + .orElseGet(() -> Assertions.fail("No fitting EReference could be found in '%s'.".formatted(eClassWithActorParameter.getName()))); + + var actorCreationToolName = "New Actor"; + + Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set); + + Runnable createNodeRunnable = this.creationTestsService.createNodeWithSelectionDialogWithoutSelectionProvided(diagramDescriptionIdProvider, diagram, eClassWithActorParameter, targetObjectId, actorCreationToolName); + + Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { + var initialDiagram = diagram.get(); + new CheckDiagramElementCount(this.diagramComparator) + .hasNewNodeCount(1) + .hasNewEdgeCount(1) + .check(initialDiagram, newDiagram, true); + new CheckNodeInCompartment(diagramDescriptionIdProvider, this.diagramComparator) + .withTargetObjectId(targetObjectId) + .withCompartmentName("actors") + .hasNodeDescriptionName(this.descriptionNameGenerator.getCompartmentItemName(eClassWithActorParameter, actorParameterEReference)) + .hasCompartmentCount(0) + .check(initialDiagram, newDiagram); + }); + + Consumer additionalCheck = referencedObject -> { + assertThat(referencedObject).isInstanceOf(List.class) + .asInstanceOf(type(List.class)) + .satisfies(actors -> { + assertThat(actors).size().isEqualTo(1); + assertThat(actors.getFirst()) + .isInstanceOf(PartUsage.class) + .asInstanceOf(type(PartUsage.class)) + .satisfies(actorPartUsage -> { + assertThat(actorPartUsage.getOwnedSpecialization()).allMatch(Specialization::isIsImplied); + assertThat(actorPartUsage.getType()) + .isNotEmpty() + .allMatch(Element::isIsLibraryElement); + }); + }); + }; + + Runnable semanticCheck = this.semanticCheckerService.checkEditingContext( + this.semanticCheckerService.getElementInParentSemanticChecker(parentNodeLabel, actorParameterEReference, SysmlPackage.eINSTANCE.getPartUsage(), additionalCheck)); + + StepVerifier.create(flux) + .consumeNextWith(initialDiagramContentConsumer) + .then(createNodeRunnable) + .consumeNextWith(diagramCheck) + .then(semanticCheck) .thenCancel() .verify(Duration.ofSeconds(10)); } diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVTimesliceSnapshotNodeCreationTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVTimesliceSnapshotNodeCreationTests.java index 87f5c91c9..ebbf58926 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVTimesliceSnapshotNodeCreationTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVTimesliceSnapshotNodeCreationTests.java @@ -150,7 +150,7 @@ public void createTimesliceNodeOn(EClass parentEClass, String parentTargetObject var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); - Runnable createNodeRunnable = creationTestsService.createNodeWithSelectionDialog(diagramDescriptionIdProvider, diagram, parentEClass, parentTargetObjectId, "New Timeslice", selectedObject); + Runnable createNodeRunnable = creationTestsService.createNodeWithSelectionDialogWithSingleSelection(diagramDescriptionIdProvider, diagram, parentEClass, parentTargetObjectId, "New Timeslice", selectedObject); // The created child EClass depends on the behavior of the selection dialog, here without selection Consumer diagramChecker = diagramCheckerService.siblingNodeGraphicalChecker(diagram, diagramDescriptionIdProvider, childEClass, newCompartmentCount, newNodeCount); Consumer additionalSemanticCheck = object -> { @@ -194,7 +194,7 @@ public void createSnapshotNodeOn(EClass parentEClass, String parentTargetObjectI SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID); var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider); - Runnable createNodeRunnable = creationTestsService.createNodeWithSelectionDialog(diagramDescriptionIdProvider, diagram, parentEClass, parentTargetObjectId, "New Snapshot", selectedObject); + Runnable createNodeRunnable = creationTestsService.createNodeWithSelectionDialogWithSingleSelection(diagramDescriptionIdProvider, diagram, parentEClass, parentTargetObjectId, "New Snapshot", selectedObject); // The created child EClass depends on the behavior of the selection dialog, here without selection Consumer diagramChecker = diagramCheckerService.siblingNodeGraphicalChecker(diagram, diagramDescriptionIdProvider, childEClass, newCompartmentCount, newNodeCount); Consumer additionalSemanticCheck = object -> { diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/data/GeneralViewWithTopNodesTestProjectData.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/data/GeneralViewWithTopNodesTestProjectData.java index 195c5f4e7..f5f41aa8d 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/data/GeneralViewWithTopNodesTestProjectData.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/data/GeneralViewWithTopNodesTestProjectData.java @@ -102,7 +102,7 @@ public static final class SemanticIds { public static final String PART_DEFINITION_ID = "fabf5949-463d-4292-8ccc-960645da72b2"; - public static final String PART_DEFINITION_TEXTUAL_REP_ID = "a2348331-4625-49b1-8455-2d9e4f0e1dc0"; + public static final String PART_DEFINITION_TEXTUAL_REP_ID = "b36b66bc-7a91-439e-ba1c-4e269f870f2f"; public static final String PART_USAGE_ID = "2ae87f41-e214-4a36-9985-e9ec156e28e6"; diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/services/diagrams/NodeCreationTestsService.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/services/diagrams/NodeCreationTestsService.java index a3417adc7..eae85c70c 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/services/diagrams/NodeCreationTestsService.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/services/diagrams/NodeCreationTestsService.java @@ -107,6 +107,8 @@ public Runnable createNode(DiagramDescriptionIdProvider diagramDescriptionIdProv /** * Creates a runnable that invokes a named node creation tool. * + *

It behaves as if the selection dialog was called without any selection provided.

+ * * @param diagramDescriptionIdProvider * the diagram description ID provider * @param diagram @@ -117,15 +119,34 @@ public Runnable createNode(DiagramDescriptionIdProvider diagramDescriptionIdProv * the ID of the element on which apply the tool * @param toolName * the name of the creation tool - * @param selectedObject - * the object ID selected in the selection dialog. - * Most of the time the ID is computed thanks to {@link org.eclipse.sirius.components.core.api.IIdentityService#getId(Object)}. - * Provide an empty string if the selection in the selection dialog is optional and you do not want to provide a selection. * @return a runnable that performs the node creation + */ + public Runnable createNodeWithSelectionDialogWithoutSelectionProvided(DiagramDescriptionIdProvider diagramDescriptionIdProvider, + AtomicReference diagram, EClass parentEClass, String targetObjectId, String toolName) { + return this.createNodeWithSelectionDialogWithSingleSelection(diagramDescriptionIdProvider, diagram, parentEClass, targetObjectId, toolName, ""); + } + + /** + * Creates a runnable that invokes a named node creation tool. + * + *

It behaves as if the selection dialog was called, and a single selection has been provided.

* - * @see org.eclipse.sirius.components.view.emf.diagram.SelectionDialogDescriptionConverter + * @param diagramDescriptionIdProvider + * the diagram description ID provider + * @param diagram + * the diagram reference + * @param parentEClass + * the EClass of the parent node + * @param targetObjectId + * the ID of the element on which apply the tool + * @param toolName + * the name of the creation tool + * @param selectedObject + * the object id selected in the selection dialog. + * Put an empty string if the selection is optional and you want the behavior without selection + * @return a runnable that performs the node creation */ - public Runnable createNodeWithSelectionDialog(DiagramDescriptionIdProvider diagramDescriptionIdProvider, + public Runnable createNodeWithSelectionDialogWithSingleSelection(DiagramDescriptionIdProvider diagramDescriptionIdProvider, AtomicReference diagram, EClass parentEClass, String targetObjectId, String toolName, String selectedObject) { return this.createNode(diagramDescriptionIdProvider, diagram, parentEClass, targetObjectId, toolName, List.of(new ToolVariable("selectedObject", selectedObject, ToolVariableType.OBJECT_ID))); } diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/ActorCompartmentNodeToolProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/ActorCompartmentNodeToolProvider.java index dd691c513..793f62b75 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/ActorCompartmentNodeToolProvider.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/ActorCompartmentNodeToolProvider.java @@ -46,8 +46,17 @@ protected SelectionDialogDescription getSelectionDialogDescription() { return this.diagramBuilderHelper.newSelectionDialogDescription() .selectionDialogTreeDescription(selectionDialogTree) .defaultTitleExpression(this.getNodeToolName()) - .descriptionExpression("Select an existing Part as actor:") - .optional(false) + .noSelectionTitleExpression(this.getNodeToolName()) + .withSelectionTitleExpression(this.getNodeToolName()) + .descriptionExpression("Create an actor:") + .noSelectionActionLabelExpression("Create a new actor") + .noSelectionActionDescriptionExpression("Create a new actor without specialization") + .withSelectionActionLabelExpression("Select an existing Element as actor") + .withSelectionActionDescriptionExpression("Create a new specialized actor") + .noSelectionActionStatusMessageExpression("It will create a new actor without specialization") + .selectionRequiredWithoutSelectionStatusMessageExpression("Select one Element to specialize the new actor") + .selectionRequiredWithSelectionStatusMessageExpression(AQLConstants.AQL + "'It will create an actor specialized with ' + selectedObjects->first().name") + .optional(true) .build(); } diff --git a/doc/content/modules/user-manual/pages/release-notes/2026.5.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2026.5.0.adoc index b6d556387..a7e431b98 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2026.5.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2026.5.0.adoc @@ -24,6 +24,7 @@ ** Add tools to create timeslices and snapshots, available on `OccurrenceUsage`, `ItemUsage`, and `PartUsage` graphical nodes. These tools leverage a selection dialog to either create a specific timeslice/snapshot graphical node or, if no selection is made, default to a timeslice/snapshot `OccurrenceUsage` graphical node. +** Improve the tool to create an `Actor` graphical node by making specialization selection optional. == Technical details