diff --git a/examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx b/examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx index ff57d3d8..d777531e 100644 --- a/examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx +++ b/examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx @@ -17,7 +17,7 @@ describe(': Basic Props', () => { it('drags a node', () => { const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform'); - cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: any) => { + cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: JQuery) => { const styleAfterDrag = $el.css('transform'); expect(styleBeforeDrag).to.not.equal(styleAfterDrag); }); @@ -43,7 +43,7 @@ describe(': Basic Props', () => { it('can not drag a node', () => { const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform'); - cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: any) => { + cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: JQuery) => { const styleAfterDrag = $el.css('transform'); expect(styleBeforeDrag).to.equal(styleAfterDrag); }); @@ -63,7 +63,7 @@ describe(': Basic Props', () => { it('drags a node', () => { const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform'); - cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: any) => { + cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: JQuery) => { const styleAfterDrag = $el.css('transform'); expect(styleBeforeDrag).to.not.equal(styleAfterDrag); }); @@ -195,5 +195,3 @@ describe(': Basic Props', () => { cy.get('.react-flow').should('have.class', 'custom'); }); }); - -// test specific helper component diff --git a/examples/nextjs/cypress/components/reactflow/event-handlers.cy.tsx b/examples/nextjs/cypress/components/reactflow/event-handlers.cy.tsx new file mode 100644 index 00000000..c75e8918 --- /dev/null +++ b/examples/nextjs/cypress/components/reactflow/event-handlers.cy.tsx @@ -0,0 +1,168 @@ +import { skipOn } from '@cypress/skip-test'; + +import { nodes, edges } from '../../fixtures/simpleflow'; +import ControlledFlow from '../../support/ControlledFlow'; + +describe(': Event handlers', () => { + describe('Node event handlers', () => { + it('handles onInit', () => { + const onInitSpy = cy.spy().as('onInitSpy'); + + cy.mount() + .wait(100) + .then(() => { + expect(onInitSpy.callCount).to.be.eq(1); + }); + }); + + it('handles onNodeClick', () => { + const onNodeClickSpy = cy.spy().as('onNodeClickSpy'); + + cy.mount().then(() => { + expect(onNodeClickSpy.callCount).to.be.eq(0); + cy.get('.react-flow__node:first') + .click() + .then(() => { + expect(onNodeClickSpy.callCount).to.be.eq(1); + }); + }); + }); + + it('handles onNodeDrag handlers', () => { + const onNodeDragStart = cy.spy().as('onNodeDragStart'); + const onNodeDrag = cy.spy().as('onNodeDrag'); + const onNodeDragStop = cy.spy().as('onNodeDragStop'); + + cy.mount( + + ).then(() => { + expect(onNodeDragStart.callCount).to.be.eq(0); + expect(onNodeDrag.callCount).to.be.eq(0); + expect(onNodeDragStop.callCount).to.be.eq(0); + + cy.drag('.react-flow__node:first', { x: 200, y: 0 }).then(() => { + expect(onNodeDragStart.callCount).to.be.eq(1); + expect(onNodeDrag.callCount).to.be.eq(1); + expect(onNodeDragStop.callCount).to.be.eq(1); + }); + }); + }); + + it('handles onNodeMouse handlers', () => { + const onNodeMouseEnter = cy.spy().as('onNodeMouseEnter'); + const onNodeMouseMove = cy.spy().as('onNodeMouseMove'); + const onNodeMouseLeave = cy.spy().as('onNodeMouseLeave'); + const onNodeContextMenu = cy.spy().as('onNodeContextMenu'); + const onNodeDoubleClick = cy.spy().as('onNodeDoubleClick'); + + cy.mount( + + ) + .wait(200) + .then(() => { + expect(onNodeMouseEnter.callCount).to.be.eq(0); + expect(onNodeMouseMove.callCount).to.be.eq(0); + expect(onNodeMouseLeave.callCount).to.be.eq(0); + expect(onNodeContextMenu.callCount).to.be.eq(0); + expect(onNodeDoubleClick.callCount).to.be.eq(0); + + const node = cy.get('.react-flow__node').contains('Node 1'); + + node + .rightclick() + .dblclick() + .then(() => { + expect(onNodeContextMenu.callCount).to.be.eq(1); + expect(onNodeDoubleClick.callCount).to.be.eq(1); + }); + + skipOn('firefox'); + + node + .realHover() + .realMouseMove(100, 100) + .then(() => { + expect(onNodeMouseEnter.callCount).to.be.gt(0); + expect(onNodeMouseMove.callCount).to.be.gt(0); + expect(onNodeMouseLeave.callCount).to.be.gt(0); + }); + }); + }); + }); + describe('Edge event handlers', () => { + it('handles onEdgeClick', () => { + const onEdgeClick = cy.spy().as('onEdgeClick'); + + cy.mount().then(() => { + expect(onEdgeClick.callCount).to.be.eq(0); + cy.get('.react-flow__edge:first') + .click() + .then(() => { + expect(onEdgeClick.callCount).to.be.eq(1); + }); + }); + }); + + it('handles onEdgeMouse handlers', () => { + const onEdgeMouseEnter = cy.spy().as('onEdgeMouseEnter'); + const onEdgeMouseMove = cy.spy().as('onEdgeMouseMove'); + const onEdgeMouseLeave = cy.spy().as('onEdgeMouseLeave'); + const onEdgeContextMenu = cy.spy().as('onEdgeContextMenu'); + const onEdgeDoubleClick = cy.spy().as('onEdgedoubleClick'); + + cy.mount( + + ) + .wait(200) + .then(() => { + expect(onEdgeMouseEnter.callCount).to.be.eq(0); + expect(onEdgeMouseMove.callCount).to.be.eq(0); + expect(onEdgeMouseLeave.callCount).to.be.eq(0); + expect(onEdgeContextMenu.callCount).to.be.eq(0); + expect(onEdgeDoubleClick.callCount).to.be.eq(0); + + const edge = cy.get('.react-flow__edge:first'); + + edge + .rightclick() + .dblclick() + .then(() => { + expect(onEdgeContextMenu.callCount).to.be.eq(1); + }); + + skipOn('firefox'); + + edge + .realHover() + .realMouseMove(100, 100) + .then(() => { + expect(onEdgeMouseEnter.callCount).to.be.gt(0); + expect(onEdgeMouseMove.callCount).to.be.gt(0); + expect(onEdgeMouseLeave.callCount).to.be.gt(0); + }); + }); + }); + }); +}); diff --git a/examples/nextjs/cypress/components/reactflow/uncontrolled.cy.tsx b/examples/nextjs/cypress/components/reactflow/uncontrolled.cy.tsx index e0f6cfaa..5a5a264a 100644 --- a/examples/nextjs/cypress/components/reactflow/uncontrolled.cy.tsx +++ b/examples/nextjs/cypress/components/reactflow/uncontrolled.cy.tsx @@ -21,7 +21,7 @@ describe(': Uncontrolled Flow', () => { it('drags a node', () => { const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform'); - cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: any) => { + cy.drag('.react-flow__node:first', { x: 200, y: 25 }).then(($el: JQuery) => { const styleAfterDrag = $el.css('transform'); expect(styleBeforeDrag).to.not.equal(styleAfterDrag); }); diff --git a/examples/nextjs/cypress/support/commands.ts b/examples/nextjs/cypress/support/commands.ts index 972d5d6a..eaca8e6f 100644 --- a/examples/nextjs/cypress/support/commands.ts +++ b/examples/nextjs/cypress/support/commands.ts @@ -1,5 +1,3 @@ -/// - Cypress.Commands.add('drag', (selector, { x, y }) => cy.window().then((window) => { const elementToDrag = cy.get(selector as string); @@ -11,7 +9,7 @@ Cypress.Commands.add('drag', (selector, { x, y }) => const nextY: number = centerY + y; return elementToDrag - .trigger('mousedown', { which: 1, view: window }) + .trigger('mousedown', { view: window }) .trigger('mousemove', nextX, nextY, { force: true }) .wait(50) .trigger('mouseup', { view: window, force: true }); diff --git a/examples/nextjs/cypress/support/component.ts b/examples/nextjs/cypress/support/component.ts index a84b87c0..85a19ff0 100644 --- a/examples/nextjs/cypress/support/component.ts +++ b/examples/nextjs/cypress/support/component.ts @@ -1,23 +1,9 @@ -// *********************************************************** -// This example support/component.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** +/// +/// +/// -// Import commands.js using ES2015 syntax: import './commands'; - -// Alternatively you can use CommonJS syntax: -// require('./commands') +import 'cypress-real-events/support'; import { mount } from 'cypress/react18'; import { XYPosition } from 'reactflow'; @@ -26,6 +12,7 @@ import '../../styles/globals.css'; import '../../styles/rf-style.css'; declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface Chainable { mount: typeof mount; diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index f34b365a..be6e7013 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -21,8 +21,10 @@ "reactflow": "workspace:*" }, "devDependencies": { + "@cypress/skip-test": "^2.6.1", "@types/dagre": "^0.7.47", "cypress": "^10.6.0", + "cypress-real-events": "^1.7.1", "eslint": "^8.22.0", "eslint-config-next": "12.2.2", "next-global-css": "^1.3.1", diff --git a/examples/nextjs/tsconfig.json b/examples/nextjs/tsconfig.json index 09130c19..8fe71c7d 100644 --- a/examples/nextjs/tsconfig.json +++ b/examples/nextjs/tsconfig.json @@ -15,7 +15,7 @@ "jsx": "preserve", "incremental": true, "baseUrl": "./", - "types": ["cypress"] + "types": ["cypress", "cypress-real-events"] }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] diff --git a/packages/core/src/hooks/useDrag/index.ts b/packages/core/src/hooks/useDrag/index.ts index 1d91a3f5..025621d4 100644 --- a/packages/core/src/hooks/useDrag/index.ts +++ b/packages/core/src/hooks/useDrag/index.ts @@ -153,7 +153,7 @@ function useDrag({ .filter((event: MouseEvent) => { const target = event.target as HTMLDivElement; const isDraggable = - event.button === 0 && + !event.button && (!noDragClassName || !hasSelector(target, `.${noDragClassName}`, nodeRef)) && (!handleSelector || hasSelector(target, handleSelector, nodeRef)); diff --git a/yarn.lock b/yarn.lock index 6813ddbc..838caa84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1908,6 +1908,13 @@ __metadata: languageName: node linkType: hard +"@cypress/skip-test@npm:^2.6.1": + version: 2.6.1 + resolution: "@cypress/skip-test@npm:2.6.1" + checksum: 48ec912f162563ca8657b6f023b701f66ddc29083ed36ae96cd861087c828046c2abb36c6fa0b316bb74941a17b046ac3b143532218e4cdc6d78d7838a64a141 + languageName: node + linkType: hard + "@cypress/xvfb@npm:^1.2.4": version: 1.2.4 resolution: "@cypress/xvfb@npm:1.2.4" @@ -4330,6 +4337,15 @@ __metadata: languageName: node linkType: hard +"cypress-real-events@npm:^1.7.1": + version: 1.7.1 + resolution: "cypress-real-events@npm:1.7.1" + peerDependencies: + cypress: ^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x + checksum: b31c2facfa03e01e298926cd0925260b12474770fc1a3ce8998da21818db7e6d9fc2f9eb60d1771aa4ce3c29aca63d04da21e1a63e3bb117b1506a72ab0c3eb1 + languageName: node + linkType: hard + "cypress@npm:^10.6.0": version: 10.6.0 resolution: "cypress@npm:10.6.0" @@ -8462,9 +8478,11 @@ __metadata: version: 0.0.0-use.local resolution: "reactflow-examples@workspace:examples/nextjs" dependencies: + "@cypress/skip-test": ^2.6.1 "@preconstruct/next": ^4.0.0 "@types/dagre": ^0.7.47 cypress: ^10.6.0 + cypress-real-events: ^1.7.1 dagre: ^0.8.5 eslint: ^8.22.0 eslint-config-next: 12.2.2