jomarko commented on code in PR #3556: URL: https://github.com/apache/incubator-kie-tools/pull/3556#discussion_r3186623229
########## packages/bpmn-editor/tests-e2e/__fixtures__/edges.ts: ########## @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Locator, Page } from "@playwright/test"; +import { Diagram } from "./diagram"; +import { Nodes } from "./nodes"; + +export enum EdgeType { + SEQUENCE_FLOW = "edge_sequenceFlow", + ASSOCIATION = "edge_association", +} + +export class Edges { + constructor( + public page: Page, + public nodes: Nodes, + public diagram: Diagram + ) {} + + public async get(args: { from: string; to: string }): Promise<Locator> { + const fromId = args.from.startsWith("_") ? args.from : await this.nodes.getId({ name: args.from }); + + const toId = args.to.startsWith("_") ? args.to : await this.nodes.getId({ name: args.to }); + + await this.page.locator(".react-flow__edge").first().waitFor({ state: "attached", timeout: 15000 }); + + const allEdges = this.page.locator(".react-flow__edge"); + const edgeCount = await allEdges.count(); + + if (edgeCount === 1) { + return allEdges.first(); + } + + const fromNode = this.nodes.getById({ id: fromId }); + const toNode = this.nodes.getById({ id: toId }); + + const fromBox = await fromNode.boundingBox(); + const toBox = await toNode.boundingBox(); + + if (!fromBox || !toBox) { + throw new Error(`Could not get bounding boxes for nodes ${fromId} and ${toId}`); + } + + const toCenter = { x: toBox.x + toBox.width / 2, y: toBox.y + toBox.height / 2 }; + + let bestMatch: { edge: Locator; distance: number } | null = null; + + for (let i = 0; i < edgeCount; i++) { + const edge = allEdges.nth(i); + + const pathElement = edge.locator("path.xyflow-react-kie-diagram--edge").first(); + const pathBox = await pathElement.boundingBox(); + + if (!pathBox) continue; + + const edgeEndX = pathBox.x + pathBox.width; + const edgeEndY = pathBox.y + pathBox.height / 2; + + const distanceToTarget = Math.sqrt(Math.pow(edgeEndX - toCenter.x, 2) + Math.pow(edgeEndY - toCenter.y, 2)); + + if (!bestMatch || distanceToTarget < bestMatch.distance) { + bestMatch = { edge, distance: distanceToTarget }; + } + } + + if (bestMatch) { + return bestMatch.edge; + } + + throw new Error( + `Could not find edge from ${args.from} (${fromId}) to ${args.to} (${toId}). Found ${edgeCount} edges.` + ); + } + + public async getType(args: { from: string; to: string }): Promise<EdgeType> { + const edge = await this.get(args); + const type = await edge.getAttribute("data-edgetype"); + return type as EdgeType; + } + + public async delete(args: { from: string; to: string }) { + const edge = await this.get(args); + await edge.click(); + await this.diagram.get().press("Delete"); + } + + public async moveWaypoint(args: { + from: string; + to: string; + waypointIndex: number; + targetPosition: { x: number; y: number }; + }) { + const edge = await this.get(args); + const waypoint = edge.locator(`[data-waypointindex="${args.waypointIndex}"]`); + await waypoint.dragTo(this.diagram.get(), { targetPosition: args.targetPosition }); + } + + public async deleteWaypoint(args: { from: string; to: string; waypointIndex: number }) { + const edge = await this.get(args); + const waypoint = edge.locator(`[data-waypointindex="${args.waypointIndex}"]`); + await waypoint.click({ button: "right" }); + await this.page.getByText("Delete waypoint").click(); + } Review Comment: Do we have tests where we test behavior of multiple waypoints on the edge? asking because if most tests use the `waypointIndex` equal to `0` maybe we could have a default value here? and then invocation in actual tests would be shorter and more readbale? ########## packages/bpmn-editor/tests-e2e/__fixtures__/editor.ts: ########## @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Page } from "@playwright/test"; +import { Nodes } from "./nodes"; + +export class Editor { + constructor( + public page: Page, + public baseURL?: string + ) {} + + public async open() { + await this.page.goto(`${this.baseURL}/iframe.html?args=&id=misc-empty--empty&viewMode=story`, { + timeout: 180000, + }); + await this.initializeEditor(); + } + + public async openWithLocale(locale: string) { + await this.page.goto(`${this.baseURL}/iframe.html?args=locale:${locale}&id=misc-empty--empty&viewMode=story`, { + timeout: 180000, + }); + await this.initializeEditor(); + } + + private async initializeEditor() { + await this.page.locator(".react-flow").waitFor({ state: "visible", timeout: 180000 }); + + const processIdInput = this.page.getByPlaceholder("e.g., hiring"); + if (await processIdInput.isVisible().catch(() => false)) { + await processIdInput.fill("test"); + await this.page.getByRole("button", { name: "Start Modeling" }).click(); + await this.page.locator(".react-flow").waitFor({ state: "visible" }); + } + } + + public async openCustomTasks({ nodes }: { nodes: Nodes }) { + await this.page.goto(`${this.baseURL}/iframe.html?args=&id=features-customtasks--custom-tasks&viewMode=story`); + await nodes.delete({ name: "Rest API call Task" }); + await nodes.delete({ name: "gRPC API call Task" }); + } Review Comment: why do we delete some nodes as part of open custom tasks operation? it sounds as unexpected, maybe is just my not enough knowledge, could you please clarify? ########## packages/bpmn-editor/tests-e2e/__screenshots__/chromium/propertiesPanel/end-event-message.png: ########## Review Comment: I think similar test should set `Message` in properties panel, or am I missing something, do we have such test elsewhere? ########## packages/bpmn-editor/tests-e2e/__fixtures__/propertiesPanel/gatewayPropertiesPanel.ts: ########## @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Page } from "@playwright/test"; +import { PropertiesPanelBase } from "./propertiesPanelBase"; +import { Diagram } from "../diagram"; +import { NameProperties } from "./parts/nameProperties"; +import { DocumentationProperties } from "./parts/documentationProperties"; + +export class GatewayPropertiesPanel extends PropertiesPanelBase { + private nameProperties: NameProperties; + private documentationProperties: DocumentationProperties; + + constructor( + public diagram: Diagram, + public page: Page + ) { + super(diagram, page); + this.nameProperties = new NameProperties(this.panel(), page); + this.documentationProperties = new DocumentationProperties(this.panel(), page); + } + + public async setName(args: { newName: string }) { + await this.nameProperties.setName({ ...args }); + } + + public async getName(): Promise<string> { + return await this.nameProperties.getName(); + } + + public async setDocumentation(args: { newDocumentation: string }) { + await this.documentationProperties.setDocumentation({ ...args }); + } + + public async getDocumentation(): Promise<string> { + return await this.documentationProperties.getDocumentation(); + } + + public async setDefaultFlow(args: { flowId: string }) { + const defaultFlowSelect = this.panel().locator("select").first(); + await defaultFlowSelect.waitFor({ state: "visible", timeout: 10000 }); + await defaultFlowSelect.selectOption(args.flowId); + } + + public async getDefaultFlow(): Promise<string> { + const defaultFlowSelect = this.panel().locator("select").first(); + await defaultFlowSelect.waitFor({ state: "visible", timeout: 10000 }); + return await defaultFlowSelect.inputValue(); + } + + private async openMorphingPanel() { + const selectedGateway = this.page.locator(".kie-bpmn-editor--selected-gateway-node").first(); + await selectedGateway.waitFor({ state: "attached", timeout: 5000 }); + + const box = await selectedGateway.boundingBox(); + if (!box) throw new Error("Gateway not visible"); + + await this.page.mouse.move(box.x + box.width / 2, box.y + box.height / 2); + await this.page.waitForTimeout(300); + + const morphingToggle = selectedGateway.locator(".kie-bpmn-editor--node-morphing-panel-toggle > div"); + await morphingToggle.waitFor({ state: "visible", timeout: 5000 }); + await morphingToggle.click({ force: true }); + + const morphingPanel = this.page.locator(".kie-bpmn-editor--node-morphing-panel"); + await morphingPanel.waitFor({ state: "visible", timeout: 5000 }); + + return morphingPanel; + } Review Comment: When I search for occurrences of `page.locator(".kie-bpmn-editor--node-morphing-panel");` I get 14 matches. Based on my understanding after reading the code, we currently have 14 different places where node morphing is handled. However, most of these implementations are very similar—if not direct copies of each other. ### Concerns The issues I’m concerned about are: - **Code maintenance** If the morphing implementation changes, maintaining and updating the codebase will be difficult. - **Inconsistent locations** In some cases, morphing is implemented directly in the test body, while in others it’s handled via a fixture. - **Misleading fixture naming** The `propertiesPanel` fixture name suggests actions related to the properties panel, but node morphing is actually an action performed on the diagram canvas. ### Why this matters These points are especially important for future test contributors. Someone contributing to the codebase without prior knowledge may encounter 14 separate, non-unified implementations of the same morphing logic, which could be quite confusing. ########## packages/bpmn-editor/package.json: ########## @@ -16,9 +16,13 @@ "start": "run-script-os", "start:linux:darwin": "cross-env STORYBOOK_PORT=$(build-env bpmnEditor.storybook.port) pnpm exec kie-tools--storybook --storybookArgs=\"dev --no-open\"", "start:win32": "pnpm powershell \"cross-env STORYBOOK_PORT=$(build-env bpmnEditor.storybook.port) pnpm exec kie-tools--storybook --storybookArgs='dev --no-open'", - "test-e2e": "run-script-if --ignore-errors \"$(build-env endToEndTests.ignoreFailures)\" --bool \"$(build-env endToEndTests.run)\" --then \"rimraf ./dist-tests-e2e\" \"pnpm test-e2e:run\"", + "test-e2e": "run-script-if --ignore-errors \"$(build-env endToEndTests.ignoreFailures)\" --bool \"$(build-env endToEndTests.run)\" --then \"pnpm test-e2e:condition\"", + "test-e2e:condition": "run-script-if --bool \"$(build-env endToEndTests.containerized)\" --then \"pnpm rimraf ./dist-tests-e2e\" \"pnpm test-e2e:container:run\" --else \"pnpm rimraf ./dist-tests-e2e\" \"pnpm test-e2e:run\"", + "test-e2e:container:clean": "playwright-base-container clean", + "test-e2e:container:run": "start-server-and-test 'pnpm start' http://localhost:$(build-env bpmnEditor.storybook.port)/iframe.html \"playwright-base-container run --additional-env=KIE_TOOLS_PLAYWRIGHT_CONTAINER__PORT=$(build-env bpmnEditor.storybook.port) --container-workdir=incubator-kie-tools/packages/bpmn-editor --container-name=kie-tools-playwright-containerization-bpmn-editor -- $@\"", + "test-e2e:container:shell": "start-server-and-test 'pnpm start' http://localhost:$(build-env bpmnEditor.storybook.port)/iframe.html 'playwright-base-container shell --additional-env=KIE_TOOLS_PLAYWRIGHT_CONTAINER__PORT=$(build-env bpmnEditor.storybook.port) --container-workdir=incubator-kie-tools/packages/bpmn-editor --container-name=kie-tools-playwright-containerization-bpmn-editor'", "test-e2e:open": "pnpm exec playwright show-report dist-tests-e2e/reports", - "test-e2e:run": "pnpm exec playwright test --pass-with-no-tests" + "test-e2e:run": "pnpm exec playwright test --update-snapshots" Review Comment: I am not sure about this change, to me it sounds as it will always update screenshot so the test will never start to fail? ########## packages/bpmn-editor/tests-e2e/__screenshots__/chromium/propertiesPanel/start-event-message.png: ########## Review Comment: similar question about `Message` in properties panel ########## packages/bpmn-editor/tests-e2e/__screenshots__/chromium/propertiesPanel/start-event-signal.png: ########## Review Comment: Similar question about `Signal` in properties panel ########## packages/bpmn-editor/tests-e2e/flowElements/boundaryEventCompensation.spec.ts: ########## @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { test, expect } from "../__fixtures__/base"; +import { NodeType } from "../__fixtures__/nodes"; + +test.beforeEach(async ({ editor }) => { + await editor.open(); +}); + +test.describe("Compensation Boundary Events", () => { + test("should create compensation boundary event on task", async ({ + palette, + jsonModel, + page, + intermediateEventPropertiesPanel, + }) => { + await palette.dragNewNode({ type: NodeType.TASK, targetPosition: { x: 300, y: 300 } }); + await palette.dragNewNode({ type: NodeType.INTERMEDIATE_CATCH_EVENT, targetPosition: { x: 450, y: 300 } }); + + const boundaryEvent = await jsonModel.getFlowElement({ elementIndex: 1 }); + expect(boundaryEvent.__$$element).toBe("boundaryEvent"); + expect(boundaryEvent["@_attachedToRef"]).toBeDefined(); + + const eventNode = page.locator(".kie-bpmn-editor--intermediate-catch-event-node").first(); + await eventNode.click(); + + await intermediateEventPropertiesPanel.setCompensationDefinition({}); + await intermediateEventPropertiesPanel.setCancelActivity({ cancelActivity: false }); + + await expect + .poll( + async () => { + return await jsonModel.getFlowElement({ elementIndex: 1 }); + }, + { timeout: 10000 } + ) + .toMatchObject({ + __$$element: "boundaryEvent", + "@_attachedToRef": expect.stringMatching(/.+/), + "@_cancelActivity": false, + eventDefinition: [{ __$$element: "compensateEventDefinition" }], + }); + + const cancelActivity = await intermediateEventPropertiesPanel.getCancelActivity(); + expect(cancelActivity).toBe(false); + }); + + test.skip("should not allow incoming sequence flows to compensation boundary event", async ({ + // TODO: Enable when compensation boundary event validation is implemented Review Comment: do we have an existing kie-issue ticket? ########## packages/bpmn-editor/tests-e2e/propertiesPanel/changeSubProcessProperties.spec.ts: ########## @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { test, expect } from "../__fixtures__/base"; +import { DefaultNodeName, NodeType } from "../__fixtures__/nodes"; + +test.beforeEach(async ({ editor, page }) => { + await page.setViewportSize({ width: 1920, height: 1080 }); + await editor.open(); +}); + +test.describe("Change Properties - Sub-Process", () => { + test.beforeEach(async ({ palette, nodes, page }) => { + await palette.dragNewNode({ type: NodeType.SUB_PROCESS, targetPosition: { x: 200, y: 200 } }); + + await expect(nodes.get({ name: DefaultNodeName.SUB_PROCESS })).toBeAttached(); + }); + + test("should change the Sub-Process name", async ({ subProcessPropertiesPanel }) => { + await subProcessPropertiesPanel.setName({ newName: "Order Processing" }); + + expect(await subProcessPropertiesPanel.getName()).toBe("Order Processing"); + }); + + test("should change the Sub-Process documentation", async ({ subProcessPropertiesPanel }) => { + await subProcessPropertiesPanel.setDocumentation({ + newDocumentation: "This sub-process handles order processing logic", + }); + + expect(await subProcessPropertiesPanel.getDocumentation()).toBe("This sub-process handles order processing logic"); + }); +}); + +test.describe("Change Properties - Sub-Process Multi-Instance", () => { + test.beforeEach(async ({ palette, nodes, subProcessPropertiesPanel, page }) => { + await palette.dragNewNode({ type: NodeType.SUB_PROCESS, targetPosition: { x: 200, y: 200 } }); + + const subProcess = nodes.get({ name: DefaultNodeName.SUB_PROCESS }); + await expect(subProcess).toBeAttached(); + + const box = await subProcess.boundingBox(); + if (!box) throw new Error("Sub-Process not visible"); + + await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2); + + const morphingToggle = subProcess.locator(".kie-bpmn-editor--node-morphing-panel-toggle > div"); + await expect(morphingToggle).toBeVisible({ timeout: 5000 }); + await morphingToggle.click({ force: true }); + + const morphingPanel = page.locator(".kie-bpmn-editor--node-morphing-panel"); + const multiInstanceOption = morphingPanel.getByTitle("Multi-instance"); + await expect(multiInstanceOption).toBeVisible({ timeout: 5000 }); + await multiInstanceOption.click({ force: true }); + }); + + test("should configure Sub-Process multi-instance parallel", async ({ subProcessPropertiesPanel, page }) => { + await subProcessPropertiesPanel.setMultiInstance({ type: "parallel" }); + await subProcessPropertiesPanel.setCollectionExpression({ expression: "${orderItems}" }); + + await expect(page.locator(".kie-bpmn-editor--root")).toHaveScreenshot("subprocess-multi-instance-parallel.png"); + }); Review Comment: is the editor working for this scenario? the screenshot show a message `No matches` however I do not see in screenshot anything like `Create expression '${orderItems}'. Are expressions supported as collection input in BPMN editor? ########## packages/bpmn-editor/tests-e2e/__screenshots__/chromium/propertiesPanel/subprocess-multi-instance-sequential.png: ########## Review Comment: similar question here as for multiinstance collection input, is the `no matches` expected? ########## packages/bpmn-editor/tests-e2e/__screenshots__/chromium/propertiesPanel/user-task-full-configuration.png: ########## Review Comment: is the full configuration proper name here? I see in properties panel not all fields are set ########## packages/bpmn-editor/tests-e2e/flowElements/addBoundaryEvent.spec.ts: ########## @@ -0,0 +1,229 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { test, expect } from "../__fixtures__/base"; +import { NodeType, DefaultNodeName } from "../__fixtures__/nodes"; + +test.beforeEach(async ({ editor }) => { + await editor.open(); +}); + +test.describe("Add Boundary Event", () => { + test.describe("Basic attachment", () => { + test("should attach intermediate catch event to task", async ({ palette, jsonModel, page }) => { + await palette.dragNewNode({ type: NodeType.TASK, targetPosition: { x: 300, y: 300 } }); + await palette.dragNewNode({ type: NodeType.INTERMEDIATE_CATCH_EVENT, targetPosition: { x: 450, y: 300 } }); + + const boundaryEvent = await jsonModel.getFlowElement({ elementIndex: 1 }); + expect(boundaryEvent.__$$element).toBe("boundaryEvent"); + expect(boundaryEvent["@_attachedToRef"]).toBeDefined(); + + const boundaryEventNode = page.locator(".kie-bpmn-editor--intermediate-catch-event-node").first(); + await expect(boundaryEventNode).toBeAttached(); + }); + + test("should attach intermediate catch event to subprocess", async ({ palette, jsonModel, page }) => { + await palette.dragNewNode({ type: NodeType.SUB_PROCESS, targetPosition: { x: 100, y: 300 } }); + await palette.dragNewNode({ type: NodeType.INTERMEDIATE_CATCH_EVENT, targetPosition: { x: 550, y: 350 } }); + + const boundaryEvent = await jsonModel.getFlowElement({ elementIndex: 1 }); + expect(boundaryEvent.__$$element).toBe("boundaryEvent"); + expect(boundaryEvent["@_attachedToRef"]).toBeDefined(); + + const boundaryEventNode = page.locator(".kie-bpmn-editor--intermediate-catch-event-node").first(); + await expect(boundaryEventNode).toBeAttached(); + }); + + test("should attach multiple boundary events to same task", async ({ palette, jsonModel, diagram }) => { + await palette.dragNewNode({ type: NodeType.TASK, targetPosition: { x: 300, y: 300 } }); + await palette.dragNewNode({ type: NodeType.INTERMEDIATE_CATCH_EVENT, targetPosition: { x: 300, y: 280 } }); + await palette.dragNewNode({ type: NodeType.INTERMEDIATE_CATCH_EVENT, targetPosition: { x: 450, y: 300 } }); + await palette.dragNewNode({ type: NodeType.INTERMEDIATE_CATCH_EVENT, targetPosition: { x: 350, y: 350 } }); + + const process = await jsonModel.getProcess(); + const boundaryEvents = process.flowElement?.filter( + (e: { __$$element: string }) => e.__$$element === "boundaryEvent" + ); + expect(boundaryEvents?.length).toBe(3); + boundaryEvents?.forEach((event: { __$$element: string; "@_attachedToRef"?: string }) => { + expect(event.__$$element).toBe("boundaryEvent"); + expect(event["@_attachedToRef"]).toBeDefined(); + }); + + await expect(diagram.get()).toHaveScreenshot("attach-multiple-boundary-events-to-task.png"); + }); + }); + + test.describe("Detachment", () => { + test("should detach boundary event back to intermediate catch event", async ({ + palette, + jsonModel, + diagram, + page, + }) => { + await palette.dragNewNode({ type: NodeType.TASK, targetPosition: { x: 300, y: 300 } }); + await palette.dragNewNode({ type: NodeType.INTERMEDIATE_CATCH_EVENT, targetPosition: { x: 450, y: 300 } }); + + const boundaryEventNode = page.locator(".kie-bpmn-editor--intermediate-catch-event-node").first(); + await expect(boundaryEventNode).toBeVisible({ timeout: 5000 }); + + await boundaryEventNode.dragTo(diagram.get(), { targetPosition: { x: 500, y: 100 } }); + + const detachedEvent = await jsonModel.getFlowElement({ elementIndex: 1 }); + expect(detachedEvent.__$$element).toBe("intermediateCatchEvent"); + expect(detachedEvent["@_attachedToRef"]).toBeUndefined(); + + await expect(diagram.get()).toHaveScreenshot("detach-boundary-event-from-task.png"); + }); + }); + + test.describe("Interrupting vs Non-interrupting", () => { + test("should create interrupting boundary event by default", async ({ palette, jsonModel, diagram }) => { + await palette.dragNewNode({ type: NodeType.TASK, targetPosition: { x: 300, y: 300 } }); + await palette.dragNewNode({ type: NodeType.INTERMEDIATE_CATCH_EVENT, targetPosition: { x: 450, y: 300 } }); + + const boundaryEvent = await jsonModel.getFlowElement({ elementIndex: 1 }); + expect(boundaryEvent.__$$element).toBe("boundaryEvent"); + expect(boundaryEvent["@_attachedToRef"]).toBeDefined(); + expect(boundaryEvent["@_cancelActivity"]).not.toBe(false); + + await expect(diagram.get()).toHaveScreenshot("interrupting-boundary-event.png"); + }); + + test("should create non-interrupting boundary event", async ({ + palette, + jsonModel, + diagram, + page, + intermediateEventPropertiesPanel, + }) => { + await palette.dragNewNode({ type: NodeType.TASK, targetPosition: { x: 300, y: 300 } }); + await palette.dragNewNode({ type: NodeType.INTERMEDIATE_CATCH_EVENT, targetPosition: { x: 450, y: 300 } }); + + const boundaryEventNode = page.locator(".kie-bpmn-editor--intermediate-catch-event-node").first(); + await expect(boundaryEventNode).toBeVisible({ timeout: 5000 }); + await boundaryEventNode.click(); + + await intermediateEventPropertiesPanel.setCancelActivity({ cancelActivity: false }); + + await expect + .poll( + async () => { + return await jsonModel.getFlowElement({ elementIndex: 1 }); + }, + { timeout: 10000 } + ) + .toMatchObject({ + __$$element: "boundaryEvent", + "@_attachedToRef": expect.stringMatching(/.+/), + "@_cancelActivity": false, + }); + + await expect(diagram.get()).toHaveScreenshot("non-interrupting-boundary-event.png"); + }); + }); + + test.describe("Activity types", () => { + test("should attach to user task", async ({ palette, nodes, jsonModel, page, diagram }) => { + await palette.dragNewNode({ type: NodeType.TASK, targetPosition: { x: 300, y: 300 } }); + + const task = page.locator(`[data-nodelabel="${DefaultNodeName.TASK}"]`); + await nodes.morphNode({ nodeLocator: task, targetMorphType: "User task" }); + + await palette.dragNewNode({ type: NodeType.INTERMEDIATE_CATCH_EVENT, targetPosition: { x: 450, y: 300 } }); + + const boundaryEvent = await jsonModel.getFlowElement({ elementIndex: 1 }); + expect(boundaryEvent.__$$element).toBe("boundaryEvent"); + expect(boundaryEvent["@_attachedToRef"]).toBeDefined(); + + await expect(diagram.get()).toHaveScreenshot("attach-boundary-event-to-user-task.png"); + }); + + test("should attach to call activity", async ({ palette, jsonModel, diagram }) => { + await palette.dragNewNode({ type: NodeType.CALL_ACTIVITY, targetPosition: { x: 300, y: 300 } }); + await palette.dragNewNode({ type: NodeType.INTERMEDIATE_CATCH_EVENT, targetPosition: { x: 450, y: 300 } }); + + const boundaryEvent = await jsonModel.getFlowElement({ elementIndex: 1 }); + expect(boundaryEvent.__$$element).toBe("boundaryEvent"); + expect(boundaryEvent["@_attachedToRef"]).toBeDefined(); + + await expect(diagram.get()).toHaveScreenshot("attach-boundary-event-to-call-activity.png"); + }); + }); + + test.describe("Operations", () => { + test("should delete boundary event", async ({ palette, jsonModel, page }) => { + await palette.dragNewNode({ type: NodeType.TASK, targetPosition: { x: 300, y: 300 } }); + await palette.dragNewNode({ type: NodeType.INTERMEDIATE_CATCH_EVENT, targetPosition: { x: 450, y: 300 } }); + + const boundaryEventNode = page.locator(".kie-bpmn-editor--intermediate-catch-event-node").first(); + await expect(boundaryEventNode).toBeVisible({ timeout: 5000 }); + await boundaryEventNode.click(); + await boundaryEventNode.press("Delete"); + + const process = await jsonModel.getProcess(); + expect( + process.flowElement?.find((e: { __$$element: string }) => e.__$$element === "boundaryEvent") + ).toBeUndefined(); + expect(process.flowElement?.find((e: { __$$element: string }) => e.__$$element === "task")).toBeDefined(); + + await expect(boundaryEventNode).not.toBeAttached(); + }); + + test.skip("should delete task with boundary event", async ({ palette, nodes, jsonModel, diagram }) => { + // TODO: Enable when boundary event deletion logic is implemented Review Comment: I think we should refer to some kie-issue to track this properly? is there a kie-issue to implement such logic? -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
