diff --git a/cypress/e2e/10-undo-redo.cy.ts b/cypress/e2e/10-undo-redo.cy.ts index 515cc611902b9..e76091b553382 100644 --- a/cypress/e2e/10-undo-redo.cy.ts +++ b/cypress/e2e/10-undo-redo.cy.ts @@ -39,11 +39,9 @@ describe('Undo/Redo', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME); - WorkflowPage.getters.nodeConnections().first().trigger('mouseover', { force: true }); - cy.get('.connection-actions .add').invoke('show'); - cy.get('.connection-actions .add').should('be.visible'); - cy.get('.connection-actions .add').click(); - WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); + WorkflowPage.getters.nodeConnections().realHover(); + cy.get('.connection-actions .add').filter(':visible').click(); + WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME, false); WorkflowPage.actions.zoomToFit(); WorkflowPage.actions.hitUndo(); WorkflowPage.getters.canvasNodes().should('have.have.length', 3); @@ -141,8 +139,8 @@ describe('Undo/Redo', () => { it('should undo/redo deleting a connection by pressing delete button', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.getters.nodeConnections().first().trigger('mouseover', { force: true }); - cy.get('.connection-actions .delete').click(); + WorkflowPage.getters.nodeConnections().realHover(); + cy.get('.connection-actions .delete').filter(':visible').should('be.visible').click(); WorkflowPage.getters.nodeConnections().should('have.length', 0); WorkflowPage.actions.hitUndo(); WorkflowPage.getters.nodeConnections().should('have.length', 1); diff --git a/cypress/e2e/12-canvas.cy.ts b/cypress/e2e/12-canvas.cy.ts index 062412e8887a0..3651ce163a661 100644 --- a/cypress/e2e/12-canvas.cy.ts +++ b/cypress/e2e/12-canvas.cy.ts @@ -61,11 +61,9 @@ describe('Canvas Actions', () => { it('should add note between two connected nodes', () => { WorkflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.getters.nodeConnections().first().trigger('mouseover', { force: true }); - cy.get('.connection-actions .add').as('AddNodeConnectionButton'); - cy.get('@AddNodeConnectionButton').invoke('show'); - cy.get('@AddNodeConnectionButton').should('be.visible').click(); - WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME); + WorkflowPage.getters.nodeConnections().first().realHover(); + cy.get('.connection-actions .add').filter(':visible').should('be.visible').click() + WorkflowPage.actions.addNodeToCanvas(SET_NODE_NAME, false); WorkflowPage.getters.canvasNodes().should('have.length', 3); WorkflowPage.getters.nodeConnections().should('have.length', 2); // And last node should be pushed to the right @@ -219,8 +217,8 @@ describe('Canvas Actions', () => { it('should delete connections by pressing the delete button', () => { WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME); WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME); - WorkflowPage.getters.nodeConnections().first().trigger('mouseover', { force: true }); - cy.get('.connection-actions .delete').click(); + WorkflowPage.getters.nodeConnections().first().realHover(); + cy.get('.connection-actions .delete').filter(':visible').should('be.visible').click(); WorkflowPage.getters.nodeConnections().should('have.length', 0); }); diff --git a/cypress/e2e/2-credentials.cy.ts b/cypress/e2e/2-credentials.cy.ts index 4430acf0704c4..f94a11571d777 100644 --- a/cypress/e2e/2-credentials.cy.ts +++ b/cypress/e2e/2-credentials.cy.ts @@ -151,7 +151,7 @@ describe('Credentials', () => { it('should correctly render required and optional credentials', () => { workflowPage.actions.visit(); cy.waitForLoad(); - workflowPage.actions.addNodeToCanvas(PIPEDRIVE_NODE_NAME, true); + workflowPage.actions.addNodeToCanvas(PIPEDRIVE_NODE_NAME, true, true); cy.get('body').type('{downArrow}'); cy.get('body').type('{enter}'); // Select incoming authentication diff --git a/cypress/e2e/5-ndv.cy.ts b/cypress/e2e/5-ndv.cy.ts index d4829945507aa..9fead48e51a4c 100644 --- a/cypress/e2e/5-ndv.cy.ts +++ b/cypress/e2e/5-ndv.cy.ts @@ -53,7 +53,7 @@ describe('NDV', () => { }); it('should show correct validation state for resource locator params', () => { - workflowPage.actions.addNodeToCanvas('Typeform', true); + workflowPage.actions.addNodeToCanvas('Typeform', true, false); ndv.getters.container().should('be.visible'); cy.get('.has-issues').should('have.length', 0); cy.get('[class*=hasIssues]').should('have.length', 0); @@ -66,7 +66,7 @@ describe('NDV', () => { it('should show validation errors only after blur or re-opening of NDV', () => { workflowPage.actions.addNodeToCanvas('Manual Trigger'); - workflowPage.actions.addNodeToCanvas('Airtable', true); + workflowPage.actions.addNodeToCanvas('Airtable', true, true); ndv.getters.container().should('be.visible'); cy.get('.has-issues').should('have.length', 0); ndv.getters.parameterInput('table').find('input').eq(1).focus().blur(); diff --git a/cypress/pages/workflow.ts b/cypress/pages/workflow.ts index 98f408e0ed932..67a16c4421417 100644 --- a/cypress/pages/workflow.ts +++ b/cypress/pages/workflow.ts @@ -92,8 +92,11 @@ export class WorkflowPage extends BasePage { this.getters.nodeCreatorSearchBar().type('{enter}'); cy.get('body').type('{esc}'); }, - addNodeToCanvas: (nodeDisplayName: string, preventNdvClose?: boolean) => { - this.getters.nodeCreatorPlusButton().click(); + addNodeToCanvas: (nodeDisplayName: string, plusButtonClick = true, preventNdvClose?: boolean) => { + if (plusButtonClick) { + this.getters.nodeCreatorPlusButton().click(); + } + this.getters.nodeCreatorSearchBar().type(nodeDisplayName); this.getters.nodeCreatorSearchBar().type('{enter}'); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index beb5f17c9d227..9a686ab2d0383 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -23,7 +23,7 @@ // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) - +import "cypress-real-events"; import { WorkflowsPage, SigninPage, SignupPage } from '../pages'; import { N8N_AUTH_COOKIE } from '../constants'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; diff --git a/package.json b/package.json index ff0f3e858d5b7..7011e19246db6 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@types/node": "^16.11.22", "cross-env": "^7.0.3", "cypress": "^10.0.3", + "cypress-real-events": "^1.7.6", "jest": "^29.3.1", "jest-environment-jsdom": "^29.3.1", "jest-mock": "^29.3.1", diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 1f2a8db3e1703..cfb1a01aa1bc4 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -39,6 +39,11 @@ "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^5.15.3", "@fortawesome/vue-fontawesome": "^2.0.2", + "@jsplumb/browser-ui": "^5.13.2", + "@jsplumb/common": "^5.13.2", + "@jsplumb/connector-bezier": "^5.13.2", + "@jsplumb/core": "^5.13.2", + "@jsplumb/util": "^5.13.2", "axios": "^0.21.1", "codemirror-lang-html-n8n": "^1.0.0", "codemirror-lang-n8n-expression": "^0.1.0", @@ -50,7 +55,6 @@ "humanize-duration": "^3.27.2", "jquery": "^3.4.1", "jsonpath": "^1.1.1", - "jsplumb": "2.15.4", "lodash-es": "^4.17.21", "lodash.camelcase": "^4.3.0", "lodash.debounce": "^4.0.8", diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 260820b8f6deb..fdded1ad3a90a 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -1,17 +1,6 @@ import { CREDENTIAL_EDIT_MODAL_KEY } from './constants'; /* eslint-disable @typescript-eslint/no-explicit-any */ import { IMenuItem } from 'n8n-design-system'; -import { - jsPlumbInstance, - DragOptions, - DropOptions, - ElementGroupRef, - Endpoint, - EndpointOptions, - EndpointRectangle, - EndpointRectangleOptions, - EndpointSpec, -} from 'jsplumb'; import { GenericValue, IConnections, @@ -47,98 +36,6 @@ import { BulkCommand, Undoable } from '@/models/history'; export * from 'n8n-design-system/types'; -declare module 'jsplumb' { - interface PaintStyle { - stroke?: string; - fill?: string; - strokeWidth?: number; - outlineStroke?: string; - outlineWidth?: number; - } - - // Extend jsPlumb Anchor interface - interface Anchor { - lastReturnValue: number[]; - } - - interface Connection { - __meta?: { - sourceNodeName: string; - sourceOutputIndex: number; - targetNodeName: string; - targetOutputIndex: number; - }; - canvas?: HTMLElement; - connector?: { - setTargetEndpoint: (endpoint: Endpoint) => void; - resetTargetEndpoint: () => void; - bounds: { - minX: number; - maxX: number; - minY: number; - maxY: number; - }; - }; - - // bind(event: string, (connection: Connection): void;): void; - bind(event: string, callback: Function): void; - removeOverlay(name: string): void; - removeOverlays(): void; - setParameter(name: string, value: any): void; - setPaintStyle(arg0: PaintStyle): void; - addOverlay(arg0: any[]): void; - setConnector(arg0: any[]): void; - getUuids(): [string, string]; - } - - interface Endpoint { - endpoint: any; - elementId: string; - __meta?: { - nodeName: string; - nodeId: string; - index: number; - totalEndpoints: number; - }; - getUuid(): string; - getOverlay(name: string): any; - repaint(params?: object): void; - } - - interface N8nPlusEndpoint extends Endpoint { - setSuccessOutput(message: string): void; - clearSuccessOutput(): void; - } - - interface Overlay { - setVisible(visible: boolean): void; - setLocation(location: number): void; - canvas?: HTMLElement; - } - - interface OnConnectionBindInfo { - originalSourceEndpoint: Endpoint; - originalTargetEndpoint: Endpoint; - getParameters(): { index: number }; - } -} - -// EndpointOptions from jsplumb seems incomplete and wrong so we define an own one -export type IEndpointOptions = Omit & { - endpointStyle: EndpointStyle; - endpointHoverStyle: EndpointStyle; - endpoint?: EndpointSpec | string; - dragAllowedWhenFull?: boolean; - dropOptions?: DropOptions & { - tolerance: string; - }; - dragProxy?: - | string - | string[] - | EndpointSpec - | [EndpointRectangle, EndpointRectangleOptions & { strokeWidth: number }]; -}; - export type EndpointStyle = { width?: number; height?: number; @@ -152,21 +49,6 @@ export type EndpointStyle = { hoverMessage?: string; }; -export type IDragOptions = DragOptions & { - grid: [number, number]; - filter: string; -}; - -export type IJsPlumbInstance = Omit & { - clearDragSelection: () => void; - addEndpoint( - el: ElementGroupRef, - params?: IEndpointOptions, - referenceParams?: IEndpointOptions, - ): Endpoint | Endpoint[]; - draggable(el: {}, options?: IDragOptions): IJsPlumbInstance; -}; - export interface IUpdateInformation { name: string; key?: string; diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index dd7fa242993cb..c50c2a3107e47 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -1,5 +1,12 @@