diff --git a/cypress/support/commands.js b/cypress/support/commands.js deleted file mode 100644 index 76707060..00000000 --- a/cypress/support/commands.js +++ /dev/null @@ -1,38 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add("login", (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This is will overwrite an existing command -- -// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) - -Cypress.Commands.add('drag', (selector, { x, y }) => { - return cy - .window() - .then((window) => - cy - .get(selector) - .trigger('mousedown', { which: 1, view: window }) - .trigger('mousemove', { clientX: x, clientY: y, force: true }) - .wait(50) - .trigger('mouseup', { view: window, force: true }) - ); -}); diff --git a/cypress.config.js b/examples/nextjs/cypress.config.js similarity index 67% rename from cypress.config.js rename to examples/nextjs/cypress.config.js index 842b7fff..7e24d96b 100644 --- a/cypress.config.js +++ b/examples/nextjs/cypress.config.js @@ -7,4 +7,11 @@ module.exports = defineConfig({ viewportHeight: 720, video: false, }, + + component: { + devServer: { + framework: 'next', + bundler: 'webpack', + }, + }, }); diff --git a/examples/nextjs/cypress/components/hooks/useEdges.cy.tsx b/examples/nextjs/cypress/components/hooks/useEdges.cy.tsx new file mode 100644 index 00000000..772e6fb3 --- /dev/null +++ b/examples/nextjs/cypress/components/hooks/useEdges.cy.tsx @@ -0,0 +1,42 @@ +import { useEffect } from 'react'; +import { ReactFlow, Edge, useEdges } from '@react-flow/bundle'; + +import { nodes as initialNodes, edges as initialEdges } from '../../fixtures/simpleflow'; + +describe('useEdges.cy.tsx', () => { + it('handles edges', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + + cy.mount( + + + + ); + + cy.get('@onChangeSpy').should('have.been.calledWith', []); + cy.get('@onChangeSpy').should('have.been.calledWith', initialEdges); + }); + + it('handles defaultEdges', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + + cy.mount( + + + + ); + + cy.get('@onChangeSpy').should('have.been.calledWith', []); + cy.get('@onChangeSpy').should('have.been.calledWith', initialEdges); + }); +}); + +const HookHelperComponent = ({ onChange }: { onChange: (edges: Edge[]) => void }) => { + const edges = useEdges(); + + useEffect(() => { + onChange(edges); + }, [edges]); + + return null; +}; diff --git a/examples/nextjs/cypress/components/hooks/useNodes.cy.tsx b/examples/nextjs/cypress/components/hooks/useNodes.cy.tsx new file mode 100644 index 00000000..3946ddd1 --- /dev/null +++ b/examples/nextjs/cypress/components/hooks/useNodes.cy.tsx @@ -0,0 +1,58 @@ +import { useEffect } from 'react'; +import { Node, ReactFlow, useNodes } from '@react-flow/bundle'; + +import { nodes } from '../../fixtures/simpleflow'; + +const nodeDimensions = { + width: 100, + height: 50, +}; + +const initialNodes: Node[] = nodes.map((n) => ({ + ...n, + style: nodeDimensions, +})); + +const expectedNodes: Node[] = initialNodes.map((n) => ({ + ...n, + positionAbsolute: n.position, + ...nodeDimensions, +})); + +describe('useNodes.cy.tsx', () => { + it('handles nodes', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + + cy.mount( + + + + ); + + cy.get('@onChangeSpy').should('have.been.calledWith', []); + cy.get('@onChangeSpy').should('have.been.calledWith', expectedNodes); + }); + + it('handles defaultNodes', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + + cy.mount( + + + + ); + + cy.get('@onChangeSpy').should('have.been.calledWith', []); + cy.get('@onChangeSpy').should('have.been.calledWith', expectedNodes); + }); +}); + +const HookHelperComponent = ({ onChange }: { onChange: (nodes: Node[]) => void }) => { + const nodes = useNodes(); + + useEffect(() => { + onChange(nodes); + }, [nodes]); + + return null; +}; diff --git a/examples/nextjs/cypress/components/hooks/useOnViewportChange.cy.tsx b/examples/nextjs/cypress/components/hooks/useOnViewportChange.cy.tsx new file mode 100644 index 00000000..baf04807 --- /dev/null +++ b/examples/nextjs/cypress/components/hooks/useOnViewportChange.cy.tsx @@ -0,0 +1,96 @@ +import { ReactFlow, useOnViewportChange, Viewport } from '@react-flow/bundle'; + +describe('useOnViewportChange.cy.tsx', () => { + it('listen to viewport drag', () => { + const onStartSpy = cy.spy().as('onStartSpy'); + const onChangeSpy = cy.spy().as('onChangeSpy'); + const onEndSpy = cy.spy().as('onEndSpy'); + + cy.mount( + + + + ); + + cy.dragPane({ from: { x: 50, y: 50 }, to: { x: 50, y: 100 } }).then(() => { + cy.get('@onStartSpy').should('have.been.calledWith', { x: 0, y: 0, zoom: 1 }); + cy.get('@onChangeSpy').should('have.been.calledWith', { x: 0, y: 50, zoom: 1 }); + cy.get('@onEndSpy').should('have.been.calledWith', { x: 0, y: 50, zoom: 1 }); + }); + }); + + it('handles zoom in', () => { + const onStartSpy = cy.spy().as('onStartSpy'); + const onChangeSpy = cy.spy().as('onChangeSpy'); + const onEndSpy = cy.spy().as('onEndSpy'); + + cy.mount( + + + + ); + + cy.zoomPane(-200).then(() => { + cy.get('@onStartSpy').should('have.been.callCount', 1); + cy.get('@onChangeSpy').should('have.been.callCount', 1); + cy.get('@onEndSpy').should('have.been.callCount', 1); + + expect(getLatestViewport(onChangeSpy).zoom).to.be.gt(1); + }); + }); + + it('handles zoom out', () => { + const onStartSpy = cy.spy().as('onStartSpy'); + const onChangeSpy = cy.spy().as('onChangeSpy'); + const onEndSpy = cy.spy().as('onEndSpy'); + + cy.mount( + + + + ); + + cy.zoomPane(200).then(() => { + cy.get('@onStartSpy').should('have.been.callCount', 1); + cy.get('@onChangeSpy').should('have.been.callCount', 1); + cy.get('@onEndSpy').should('have.been.callCount', 1); + + expect(getLatestViewport(onChangeSpy).zoom).to.be.lt(1); + }); + }); + + it('handles default viewport', () => { + const defaultViewport = { x: 100, y: 100, zoom: 0.5 }; + const onStartSpy = cy.spy().as('onStartSpy'); + + cy.mount( + + + + ); + + cy.dragPane({ from: { x: 50, y: 50 }, to: { x: 50, y: 100 } }).then(() => { + cy.get('@onStartSpy').should('have.been.calledWith', defaultViewport); + }); + }); +}); + +// test specific helpers + +function HookHelperComponent({ + onStart, + onChange, + onEnd, +}: { + onStart?: (viewport: Viewport) => void; + onChange?: (viewport: Viewport) => void; + onEnd?: (viewport: Viewport) => void; +}) { + useOnViewportChange({ onStart, onChange, onEnd }); + + return null; +} + +function getLatestViewport(onChangeSpy: any) { + return onChangeSpy.lastCall.args[0] as unknown as Viewport; +} diff --git a/examples/nextjs/cypress/components/hooks/useViewport.cy.tsx b/examples/nextjs/cypress/components/hooks/useViewport.cy.tsx new file mode 100644 index 00000000..27f9eb94 --- /dev/null +++ b/examples/nextjs/cypress/components/hooks/useViewport.cy.tsx @@ -0,0 +1,79 @@ +import { ReactFlow, useViewport, Viewport } from '@react-flow/bundle'; + +describe('useViewport.cy.tsx', () => { + it('handles drag', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + + cy.mount( + + + + ); + + cy.get('@onChangeSpy').should('have.been.calledWith', { x: 0, y: 0, zoom: 1 }); + + cy.dragPane({ from: { x: 50, y: 0 }, to: { x: 50, y: 400 } }).then(() => { + cy.get('@onChangeSpy').should('have.been.callCount', 2); + cy.get('@onChangeSpy').should('have.been.calledWith', { x: 0, y: 0, zoom: 1 }); + + expect(getLatestViewport(onChangeSpy)).to.eql({ x: 0, y: 400, zoom: 1 }); + }); + }); + + it('handles zoom in', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + + cy.mount( + + + + ); + + cy.zoomPane(-200).then(() => { + cy.get('@onChangeSpy').should('have.been.callCount', 2); + expect(getLatestViewport(onChangeSpy).zoom).to.be.gt(1); + }); + }); + + it('handles zoom out', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + + cy.mount( + + + + ); + + cy.zoomPane(200).then(() => { + cy.get('@onChangeSpy').should('have.been.callCount', 2); + expect(getLatestViewport(onChangeSpy).zoom).to.be.lt(1); + }); + }); + + it('handles default viewport', () => { + const defaultViewport = { x: 100, y: 100, zoom: 0.5 }; + const onChangeSpy = cy.spy().as('onChangeSpy'); + + cy.mount( + + + + ); + + cy.get('@onChangeSpy').should('have.been.calledWith', defaultViewport); + }); +}); + +// test specific helpers + +function HookHelperComponent({ onChange }: { onChange: (vp: Viewport) => void }) { + const viewport = useViewport(); + + onChange(viewport); + + return null; +} + +function getLatestViewport(onChangeSpy: any) { + return onChangeSpy.lastCall.args[0] as unknown as Viewport; +} diff --git a/examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx b/examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx new file mode 100644 index 00000000..cd9eb7aa --- /dev/null +++ b/examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx @@ -0,0 +1,199 @@ +import { ReactFlow, EdgeProps } from '@react-flow/bundle'; + +import ControlledFlow from '../../support/ControlledFlow'; +import * as simpleflow from '../../fixtures/simpleflow'; + +describe(': Basic Props', () => { + describe('uses defaultNodes and defaultEdges', () => { + beforeEach(() => { + cy.mount(); + }); + + it('mounts nodes and edges', () => { + cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length); + cy.get('.react-flow__edge').should('have.length', simpleflow.edges.length); + }); + + 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) => { + const styleAfterDrag = $el.css('transform'); + expect(styleBeforeDrag).to.not.equal(styleAfterDrag); + }); + }); + }); + + describe('uses nodes and edges', () => { + beforeEach(() => { + cy.mount( + + ); + }); + + it('mounts nodes and edges', () => { + cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length); + cy.get('.react-flow__edge').should('have.length', simpleflow.edges.length); + }); + + 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) => { + const styleAfterDrag = $el.css('transform'); + expect(styleBeforeDrag).to.equal(styleAfterDrag); + }); + }); + }); + + describe('uses onNodesChange handler', () => { + beforeEach(() => { + cy.mount(); + }); + + it('mounts nodes', () => { + cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length); + cy.get('.react-flow__edge').should('have.length', 0); + }); + + 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) => { + const styleAfterDrag = $el.css('transform'); + expect(styleBeforeDrag).to.not.equal(styleAfterDrag); + }); + }); + + it('can not connect nodes', () => { + cy.get('.react-flow__node').first().find('.react-flow__handle.source').trigger('mousedown', { button: 0 }); + + cy.get('.react-flow__node') + .last() + .find('.react-flow__handle.target') + .trigger('mousemove') + .trigger('mouseup', { force: true }); + + cy.get('.react-flow__edge').should('have.length', 0); + }); + }); + + describe('uses onEdgeChange handler', () => { + beforeEach(() => { + cy.mount(); + }); + + it('mounts nodes and edges', () => { + cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length); + cy.get('.react-flow__edge').should('have.length', simpleflow.edges.length); + }); + + it('selects an edge', () => { + cy.get('.react-flow__edge').first().click().should('have.class', 'selected'); + }); + }); + + describe('uses onConnect handlers', () => { + beforeEach(() => { + cy.mount(); + }); + + it('mounts nodes', () => { + cy.get('.react-flow__node').should('have.length', simpleflow.nodes.length); + cy.get('.react-flow__edge').should('have.length', 0); + }); + + it('connects nodes', () => { + cy.get('.react-flow__node').first().find('.react-flow__handle.source').trigger('mousedown', { button: 0 }); + + cy.get('.react-flow__node') + .last() + .find('.react-flow__handle.target') + .trigger('mousemove') + .trigger('mouseup', { force: true }); + + cy.get('.react-flow__edge').should('have.length', 1); + }); + }); + + describe('uses nodeTypes', () => { + it('renders custom nodes', () => { + const nodeTypes = { + custom: () =>
custom node
, + }; + + const initialNodes = simpleflow.nodes.map((n) => ({ + ...n, + type: 'custom', + })); + + cy.mount(); + + cy.get('.react-flow__node-custom').should('have.length', simpleflow.nodes.length); + cy.get('.react-flow').contains('custom node'); + }); + + it('tries invalid node type - should fallback to default', () => { + const initialNodes = simpleflow.nodes.map((n) => ({ + ...n, + type: 'invalid', + })); + + cy.mount(); + + cy.get('.react-flow__node-invalid').should('have.length', 0); + cy.get('.react-flow__node-default').should('have.length', simpleflow.nodes.length); + }); + }); + + describe('uses edgeTypes', () => { + it('renders custom edge', () => { + const edgeTypes = { + custom: ({ sourceX, sourceY, targetX, targetY }: EdgeProps) => ( + + ), + }; + + const initialEdges = simpleflow.edges.map((e) => ({ + ...e, + type: 'custom', + })); + + cy.mount(); + + cy.get('.react-flow__edge-custom').should('have.length', simpleflow.edges.length); + }); + + it('tries invalid edge type - should fallback to default', () => { + const initialEdges = simpleflow.edges.map((e) => ({ + ...e, + type: 'invalid', + })); + + cy.mount(); + + cy.get('.react-flow__edge-invalid').should('have.length', 0); + cy.get('.react-flow__edge-default').should('have.length', simpleflow.edges.length); + }); + }); + + it('uses style', () => { + const styles = { + backgroundColor: 'red', + }; + + cy.mount(); + cy.get('.react-flow').should('have.css', 'background-color', 'rgb(255, 0, 0)'); + }); + + it('uses classname', () => { + cy.mount(); + cy.get('.react-flow').should('have.class', 'custom'); + }); +}); + +// test specific helper component diff --git a/examples/nextjs/cypress/components/reactflow/uncontrolled.cy.tsx b/examples/nextjs/cypress/components/reactflow/uncontrolled.cy.tsx new file mode 100644 index 00000000..09ab24a2 --- /dev/null +++ b/examples/nextjs/cypress/components/reactflow/uncontrolled.cy.tsx @@ -0,0 +1,35 @@ +import { ReactFlow } from '@react-flow/bundle'; + +import { nodes, edges } from '../../fixtures/simpleflow'; + +describe(': Uncontrolled Flow', () => { + beforeEach(() => { + cy.mount(); + }); + + it('mounts nodes and edges', () => { + cy.get('.react-flow__node').should('have.length', nodes.length); + cy.get('.react-flow__edge').should('have.length', edges.length); + }); + + it('selects a node', () => { + cy.get('.react-flow__node').first().click().should('have.class', 'selected'); + cy.get('.react-flow__pane').click(); + cy.get('.react-flow__node').first().should('not.have.class', 'selected'); + }); + + 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) => { + const styleAfterDrag = $el.css('transform'); + expect(styleBeforeDrag).to.not.equal(styleAfterDrag); + }); + }); + + it('selects an edge', () => { + cy.get('.react-flow__edge').first().click().should('have.class', 'selected'); + cy.get('.react-flow__pane').click(); + cy.get('.react-flow__edge').first().should('not.have.class', 'selected'); + }); +}); diff --git a/examples/nextjs/cypress/components/reactflow/view-props.cy.tsx b/examples/nextjs/cypress/components/reactflow/view-props.cy.tsx new file mode 100644 index 00000000..f6958d90 --- /dev/null +++ b/examples/nextjs/cypress/components/reactflow/view-props.cy.tsx @@ -0,0 +1,312 @@ +import { ReactFlow, ReactFlowProps, Viewport, useViewport, SnapGrid, CoordinateExtent, Node } from '@react-flow/bundle'; + +import ControlledFlow from '../../support/ControlledFlow'; +import * as simpleflow from '../../fixtures/simpleflow'; + +const nodesOutsideOfView = [ + { + ...simpleflow.nodes[0], + position: { + x: -500, + y: 0, + }, + }, + { + ...simpleflow.nodes[1], + position: { + x: 500, + y: 0, + }, + }, +]; + +describe(': View Props', () => { + it('uses fitView', () => { + cy.mount().wait(200); + cy.get('.react-flow__node:first').isWithinViewport(); + }); + + it('uses fitViewOptions', () => { + const fitViewOptions = { padding: 0.5 }; + + cy.mount().wait(200); + cy.get('.react-flow__node:first').isWithinViewport(); + }); + + describe('uses minZoom', () => { + it('minZoom: 0.2', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + + cy.mount(); + cy.zoomPane(10000).then(() => { + expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(0.2); + }); + }); + + it('minZoom: 1', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + + cy.mount(); + cy.zoomPane(10000).then(() => { + expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1); + }); + }); + + it('sets invalid defaultZoom', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + const defaultViewport = { x: 0, y: 0, zoom: 0.2 }; + + cy.mount(); + cy.zoomPane(10000).then(() => { + expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1); + }); + }); + }); + + describe('uses maxZoom', () => { + it('maxZoom: 1', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + + cy.mount(); + cy.zoomPane(-10000).then(() => { + expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1); + }); + }); + + it('maxZoom: 2', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + + cy.mount(); + cy.zoomPane(-10000).then(() => { + expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(2); + }); + }); + + it('sets invalid defaultZoom', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + const defaultViewport = { x: 0, y: 0, zoom: 2 }; + + cy.mount(); + cy.zoomPane(-10000).then(() => { + expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1.5); + }); + }); + }); + + describe('uses defaultViewport', () => { + it('defaultViewport: { x: 0, y: 0, zoom: 1 }', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + const defaultViewport = { x: 0, y: 0, zoom: 1 }; + + cy.mount().then(() => { + expect(getLatestViewport(onChangeSpy)).to.be.eql(defaultViewport); + }); + }); + + it('defaultViewport: { x: 0, y: 0, zoom: 1.5 }', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + const defaultViewport = { x: 0, y: 0, zoom: 1.5 }; + + cy.mount().then(() => { + expect(getLatestViewport(onChangeSpy)).to.be.eql(defaultViewport); + }); + }); + + it('defaultViewport: { x: 100, y: 100, zoom: 1.5 }', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + const defaultViewport = { x: 100, y: 100, zoom: 1.5 }; + + cy.mount().then(() => { + expect(getLatestViewport(onChangeSpy)).to.be.eql(defaultViewport); + }); + }); + + it('defaultViewport: invalid { x: 0, y: 0, zoom: -1 }', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + const defaultViewport = { x: 0, y: 0, zoom: -1 }; + + cy.mount().then(() => { + // this should be min zoom because zoom is clamped + expect(getLatestViewport(onChangeSpy).zoom).to.be.eql(0.5); + }); + }); + + it('defaultViewport: invalid { x: 0, y: 0, zoom: 100 }', () => { + const onChangeSpy = cy.spy().as('onChangeSpy'); + const defaultViewport = { x: 0, y: 0, zoom: 100 }; + + cy.mount().then(() => { + // this should be max zoom because zoom is clamped + expect(getLatestViewport(onChangeSpy).zoom).to.be.eql(2); + }); + }); + }); + + it('uses snapGrid and snapToGrid', () => { + const snapGrid: SnapGrid = [100, 100]; + + cy.mount().then(() => { + cy.drag('.react-flow__node:first', { x: 125, y: 125 }).then(($el: any) => { + const transformAfterDrag = $el.css('transform'); + const { m41: nodeX, m42: nodeY } = new DOMMatrixReadOnly(transformAfterDrag); + expect([nodeX, nodeY]).to.eql(snapGrid); + }); + }); + }); + + describe('uses onlyRenderVisibleElements', () => { + it('displays no nodes', () => { + cy.mount().then(() => { + cy.get('.react-flow__node').should('have.length', 0); + }); + }); + + it('displays one node', () => { + const nodes = nodesOutsideOfView.map((n) => { + if (n.id === '1') { + return { ...n, position: { x: 0, y: 0 } }; + } + return n; + }); + cy.mount().then(() => { + cy.get('.react-flow__node').should('have.length', 1); + }); + }); + }); + + it('uses translateExtent', () => { + const translateExtent: CoordinateExtent = [ + [0, 0], + [1000, 1000], + ]; + + cy.mount().then(() => { + const transformBeforeDrag = Cypress.$('.react-flow__viewport').css('transform'); + + cy.dragPane({ from: { x: 0, y: 0 }, to: { x: 100, y: 100 } }).then(() => { + const transformAfterDrag = Cypress.$('.react-flow__viewport').css('transform'); + expect(transformBeforeDrag).to.eql(transformAfterDrag); + }); + }); + }); + + it('uses nodeExtent', () => { + const nodes: Node[] = [ + { + id: '1', + position: { x: 200, y: 200 }, + data: { label: '1' }, + }, + ]; + + const nodeExtent: CoordinateExtent = [ + [0, 0], + [50, 50], + ]; + + cy.mount() + .wait(500) + .then(() => { + const transform = Cypress.$('.react-flow__node:first').css('transform'); + const { m41: nodeX, m42: nodeY } = new DOMMatrixReadOnly(transform); + expect(nodeX).to.equal(50); + expect(nodeY).to.equal(50); + }); + }); + + describe('uses preventScrolling', () => { + function ScrollFlow({ preventScrolling }: { preventScrolling: boolean }) { + return ( +
+
+ +
+
+ ); + } + + it('lets user scroll', () => { + cy.mount().then(() => { + cy.window().then((window) => { + cy.get('.react-flow__pane').trigger('wheel', { + wheelDelta: 240, + wheelDeltaX: 0, + wheelDeltaY: 240, + eventConstructor: 'WheelEvent', + view: window, + }); + + // @TODO: why is this not working? + // it seems that the event is somehow broken. This works fine when using the mouse. + // cy.window().its('scrollY').should('be.gt', 0); + }); + }); + }); + + it('does not user scroll', () => { + cy.mount().then(() => { + cy.window().then((window) => { + cy.get('.react-flow__pane').trigger('wheel', { + wheelDelta: 240, + wheelDeltaX: 0, + wheelDeltaY: 240, + eventConstructor: 'WheelEvent', + view: window, + }); + + cy.window().its('scrollY').should('be.equal', 0); + }); + }); + }); + }); + + describe('uses attributionPosition', () => { + it('displays it on the bottom right', () => { + cy.mount().then(() => { + cy.get('.react-flow__attribution').then(($el) => { + const { left, top, width, height } = $el[0].getBoundingClientRect(); + + expect(left).equal(window.innerWidth - width); + expect(top).equal(window.innerHeight - height); + }); + }); + }); + + it('displays it on the top left', () => { + cy.mount().then(() => { + cy.get('.react-flow__attribution').then(($el) => { + const { left, top } = $el[0].getBoundingClientRect(); + + expect(left).equal(0); + expect(top).equal(0); + }); + }); + }); + }); +}); + +// test specific helpers + +type CompProps = { + onChange?: (vp: Viewport) => void; +}; + +function InnerComp({ onChange }: CompProps) { + const viewport = useViewport(); + + onChange?.(viewport); + + return null; +} + +function Comp({ onChange, ...rest }: CompProps & ReactFlowProps) { + return ( + + + + ); +} + +function getLatestViewport(onChangeSpy: any) { + return onChangeSpy.lastCall.args[0] as unknown as Viewport; +} diff --git a/cypress/e2e/graph-utils.cy.js b/examples/nextjs/cypress/components/utils/graph-utils.cy.ts similarity index 77% rename from cypress/e2e/graph-utils.cy.js rename to examples/nextjs/cypress/components/utils/graph-utils.cy.ts index 18d9cb2c..6ebded1b 100644 --- a/cypress/e2e/graph-utils.cy.js +++ b/examples/nextjs/cypress/components/utils/graph-utils.cy.ts @@ -1,12 +1,6 @@ -import { - isNode, - isEdge, - getOutgoers, - getIncomers, - addEdge, -} from '../../packages/core/dist/react-flow-core.esm.js'; +import { Node, Edge, isNode, isEdge, getOutgoers, getIncomers, addEdge } from '@react-flow/bundle'; -const nodes = [ +const nodes: Node[] = [ { id: '1', type: 'input', @@ -18,7 +12,7 @@ const nodes = [ { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } }, ]; -const edges = [ +const edges: Edge[] = [ { id: 'e1-2', source: '1', target: '2', animated: true }, { id: 'e1-3', source: '1', target: '3' }, { id: 'e2-3', source: '2', target: '3' }, @@ -54,31 +48,32 @@ describe('Graph Utils Testing', () => { describe('tests addEdge function', () => { it('adds edge', () => { - const newEdge = { source: '1', target: '4' }; + const newEdge: Edge = { source: '1', target: '4', id: 'new-edge-1-4' }; const nextEdges = addEdge(newEdge, edges); expect(nextEdges.length).to.be.equal(edges.length + 1); }); it('tries to add existing edge', () => { - const newEdge = { source: '2', target: '3' }; + const newEdge: Edge = { source: '2', target: '3', id: 'new-edge-2-3' }; const nextEdges = addEdge(newEdge, edges); expect(nextEdges.length).to.be.equal(edges.length); }); it('tries to add invalid edge', () => { - const newEdge = { nosource: '1', notarget: '3' }; + // @ts-ignore + const newEdge: Edge = { nosource: '1', notarget: '3' }; try { addEdge(newEdge, edges); - } catch (e) { + } catch (e: any) { console.log(e.message); - expect(e.message).to.be.equal( - "Can't create edge. An edge needs a source and a target." - ); + expect(e.message).to.be.equal("Can't create edge. An edge needs a source and a target."); } }); }); }); + +export {}; diff --git a/cypress/e2e/basic.cy.js b/examples/nextjs/cypress/e2e/basic.cy.ts similarity index 91% rename from cypress/e2e/basic.cy.js rename to examples/nextjs/cypress/e2e/basic.cy.ts index c43ab716..a08ca021 100644 --- a/cypress/e2e/basic.cy.js +++ b/examples/nextjs/cypress/e2e/basic.cy.ts @@ -29,9 +29,7 @@ describe('Basic Flow Rendering', () => { }); it('selects a node by click', () => { - cy.get('.react-flow__node:first') - .click({ force: true }) - .should('have.class', 'selected'); + cy.get('.react-flow__node:first').click({ force: true }).should('have.class', 'selected'); }); it('deselects node', () => { @@ -40,9 +38,7 @@ describe('Basic Flow Rendering', () => { }); it('selects an edge by click', () => { - cy.get('.react-flow__edge:first') - .click({ force: true }) - .should('have.class', 'selected'); + cy.get('.react-flow__edge:first').click({ force: true }).should('have.class', 'selected'); }); it('deselects edge', () => { @@ -93,17 +89,13 @@ describe('Basic Flow Rendering', () => { }); it('selects an edge', () => { - cy.get('.react-flow__edge:first') - .click({ force: true }) - .should('have.class', 'selected'); + cy.get('.react-flow__edge:first').click({ force: true }).should('have.class', 'selected'); }); it('drags a node', () => { - const styleBeforeDrag = Cypress.$('.react-flow__node:first').css( - 'transform' - ); + const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform'); - cy.drag('.react-flow__node:first', { x: 500, y: 25 }).then(($el) => { + cy.drag('.react-flow__node:first', { x: 500, y: 25 }).then(($el: any) => { const styleAfterDrag = $el.css('transform'); expect(styleBeforeDrag).to.not.equal(styleAfterDrag); }); @@ -153,9 +145,7 @@ describe('Basic Flow Rendering', () => { .wait(50) .trigger('mouseup', { force: true, view: win }) .then(() => { - const styleAfterDrag = Cypress.$('.react-flow__viewport').css( - 'transform' - ); + const styleAfterDrag = Cypress.$('.react-flow__viewport').css('transform'); expect(styleBeforeDrag).to.not.equal(styleAfterDrag); }); }); @@ -168,10 +158,10 @@ describe('Basic Flow Rendering', () => { .trigger('wheel', 'topLeft', { deltaY: -200 }) .wait(50) .then(() => { - const styleAfterZoom = Cypress.$('.react-flow__viewport').css( - 'transform' - ); + const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform'); expect(styleBeforeZoom).to.not.equal(styleAfterZoom); }); }); }); + +export {}; diff --git a/cypress/e2e/controls.cy.js b/examples/nextjs/cypress/e2e/controls.cy.ts similarity index 99% rename from cypress/e2e/controls.cy.js rename to examples/nextjs/cypress/e2e/controls.cy.ts index f38df829..74548f56 100644 --- a/cypress/e2e/controls.cy.js +++ b/examples/nextjs/cypress/e2e/controls.cy.ts @@ -79,3 +79,5 @@ describe('Controls Testing', () => { }); }); }); + +export {}; diff --git a/cypress/e2e/draghandle.cy.js b/examples/nextjs/cypress/e2e/draghandle.cy.ts similarity index 98% rename from cypress/e2e/draghandle.cy.js rename to examples/nextjs/cypress/e2e/draghandle.cy.ts index 498d84d2..259fbf8a 100644 --- a/cypress/e2e/draghandle.cy.js +++ b/examples/nextjs/cypress/e2e/draghandle.cy.ts @@ -28,3 +28,5 @@ describe('DragHandle Flow Rendering', () => { }); }); }); + +export {}; diff --git a/cypress/e2e/empty.cy.js b/examples/nextjs/cypress/e2e/empty.cy.ts similarity index 69% rename from cypress/e2e/empty.cy.js rename to examples/nextjs/cypress/e2e/empty.cy.ts index ce31424f..5f25dcaf 100644 --- a/cypress/e2e/empty.cy.js +++ b/examples/nextjs/cypress/e2e/empty.cy.ts @@ -10,12 +10,15 @@ describe('Empty Flow Rendering', () => { }); it('renders empty selection', () => { + cy.get('.react-flow__renderer').click(); cy.get('body') .type('{shift}', { release: false }) + .wait(50) .get('.react-flow__selectionpane') - .trigger('mousedown', 'topLeft', { which: 1, force: true }) - .trigger('mousemove', 'bottomLeft', { which: 1 }) - .trigger('mouseup', 'bottomLeft', { force: true }); + .trigger('mousedown', 400, 50, { which: 1, force: true }) + .trigger('mousemove', 200, 200, { which: 1 }) + .wait(50) + .trigger('mouseup', 200, 200, { force: true }); cy.get('body').type('{shift}', { release: true }); }); @@ -26,10 +29,7 @@ describe('Empty Flow Rendering', () => { }); it('connects nodes', () => { - cy.get('.react-flow__node') - .first() - .find('.react-flow__handle.source') - .trigger('mousedown', { button: 0 }); + cy.get('.react-flow__node').first().find('.react-flow__handle.source').trigger('mousedown', { button: 0 }); cy.get('.react-flow__node') .last() @@ -40,3 +40,5 @@ describe('Empty Flow Rendering', () => { cy.get('.react-flow__edge').should('have.length', 1); }); }); + +export {}; diff --git a/cypress/e2e/hidden.cy.js b/examples/nextjs/cypress/e2e/hidden.cy.ts similarity index 98% rename from cypress/e2e/hidden.cy.js rename to examples/nextjs/cypress/e2e/hidden.cy.ts index 730502f3..2039d742 100644 --- a/cypress/e2e/hidden.cy.js +++ b/examples/nextjs/cypress/e2e/hidden.cy.ts @@ -30,3 +30,5 @@ describe('Hidden Flow Rendering', () => { cy.get('.react-flow__minimap-node').should('not.exist'); }); }); + +export {}; diff --git a/cypress/e2e/interaction.cy.js b/examples/nextjs/cypress/e2e/interaction.cy.ts similarity index 92% rename from cypress/e2e/interaction.cy.js rename to examples/nextjs/cypress/e2e/interaction.cy.ts index 368dd0e1..625ecc57 100644 --- a/cypress/e2e/interaction.cy.js +++ b/examples/nextjs/cypress/e2e/interaction.cy.ts @@ -11,16 +11,12 @@ describe('Interaction Flow Rendering', () => { }); it('tries to select a node by click', () => { - const pointerEvents = Cypress.$('.react-flow__node:first').css( - 'pointer-events' - ); + const pointerEvents = Cypress.$('.react-flow__node:first').css('pointer-events'); expect(pointerEvents).to.equal('none'); }); it('tries to select an edge by click', () => { - const pointerEvents = Cypress.$('.react-flow__edge:first').css( - 'pointer-events' - ); + const pointerEvents = Cypress.$('.react-flow__edge:first').css('pointer-events'); expect(pointerEvents).to.equal('none'); }); @@ -29,24 +25,17 @@ describe('Interaction Flow Rendering', () => { }); it('allows node clicks when enabled', () => { - const pointerEvents = Cypress.$('.react-flow__node:first').css( - 'pointer-events' - ); + const pointerEvents = Cypress.$('.react-flow__node:first').css('pointer-events'); expect(pointerEvents).to.equal('all'); }); it('allows edge clicks when enabled', () => { - const pointerEvents = Cypress.$('.react-flow__edge:first').css( - 'pointer-events' - ); + const pointerEvents = Cypress.$('.react-flow__edge:first').css('pointer-events'); expect(pointerEvents.toLowerCase()).to.equal('visiblestroke'); }); it('tries to do a selection', () => { - cy.get('body') - .type('{shift}', { release: false }) - .get('.react-flow__selectionpane') - .should('not.exist'); + cy.get('body').type('{shift}', { release: false }).get('.react-flow__selectionpane').should('not.exist'); cy.get('body').type('{shift}', { release: true }); }); @@ -87,9 +76,7 @@ describe('Interaction Flow Rendering', () => { }); it('drags a node', () => { - const styleBeforeDrag = Cypress.$('.react-flow__node:first').css( - 'transform' - ); + const styleBeforeDrag = Cypress.$('.react-flow__node:first').css('transform'); cy.drag('.react-flow__node:first', { x: 325, y: 100 }).then(($el) => { const styleAfterDrag = $el.css('transform'); @@ -140,9 +127,7 @@ describe('Interaction Flow Rendering', () => { .trigger('wheel', 'topLeft', { deltaY: 200 }) .wait(50) .then(() => { - const styleAfterZoom = Cypress.$('.react-flow__viewport').css( - 'transform' - ); + const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform'); expect(styleBeforeZoom).not.to.equal(styleAfterZoom); }); }); @@ -159,10 +144,10 @@ describe('Interaction Flow Rendering', () => { .dblclick() .wait(50) .then(() => { - const styleAfterZoom = Cypress.$('.react-flow__viewport').css( - 'transform' - ); + const styleAfterZoom = Cypress.$('.react-flow__viewport').css('transform'); expect(styleBeforeZoom).not.to.equal(styleAfterZoom); }); }); }); + +export {}; diff --git a/cypress/e2e/minimap.cy.js b/examples/nextjs/cypress/e2e/minimap.cy.ts similarity index 81% rename from cypress/e2e/minimap.cy.js rename to examples/nextjs/cypress/e2e/minimap.cy.ts index a61c2565..d6cb7d46 100644 --- a/cypress/e2e/minimap.cy.js +++ b/examples/nextjs/cypress/e2e/minimap.cy.ts @@ -11,29 +11,23 @@ describe('Minimap Testing', () => { it('has same number of nodes as the pane', () => { const paneNodes = Cypress.$('.react-flow__node').length; - cy.wait(100); + cy.wait(200); const minimapNodes = Cypress.$('.react-flow__minimap-node').length; - cy.wait(100); - expect(paneNodes).equal(minimapNodes); }); it('changes zoom level', () => { - const viewBoxBeforeZoom = Cypress.$('.react-flow__minimap').attr('viewBox'); + const viewBoxBeforeZoom = Cypress.$('.react-flow__minimap svg').attr('viewBox'); const maskPathBeforeZoom = Cypress.$('.react-flow__minimap-mask').attr('d'); cy.get('.react-flow__pane') .trigger('wheel', 'topLeft', { deltaY: -200 }) .wait(50) .then(() => { - const viewBoxAfterZoom = Cypress.$('.react-flow__minimap').attr( - 'viewBox' - ); - const maskPathAfterZoom = Cypress.$('.react-flow__minimap-mask').attr( - 'd' - ); + const viewBoxAfterZoom = Cypress.$('.react-flow__minimap svg').attr('viewBox'); + const maskPathAfterZoom = Cypress.$('.react-flow__minimap-mask').attr('d'); expect(viewBoxBeforeZoom).to.not.equal(viewBoxAfterZoom); expect(maskPathBeforeZoom).to.not.equal(maskPathAfterZoom); @@ -41,22 +35,14 @@ describe('Minimap Testing', () => { }); it('changes node position', () => { - const xPosBeforeDrag = Cypress.$('.react-flow__minimap-node:first').attr( - 'x' - ); - const yPosBeforeDrag = Cypress.$('.react-flow__minimap-node:first').attr( - 'y' - ); + const xPosBeforeDrag = Cypress.$('.react-flow__minimap-node:first').attr('x'); + const yPosBeforeDrag = Cypress.$('.react-flow__minimap-node:first').attr('y'); cy.drag('.react-flow__node:first', { x: 500, y: 25 }) .wait(100) .then(() => { - const xPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr( - 'x' - ); - const yPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr( - 'y' - ); + const xPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr('x'); + const yPosAfterDrag = Cypress.$('.react-flow__minimap-node:first').attr('y'); expect(xPosBeforeDrag).to.not.equal(xPosAfterDrag); expect(yPosBeforeDrag).to.not.equal(yPosAfterDrag); @@ -64,7 +50,7 @@ describe('Minimap Testing', () => { }); it('changes node positions via pane drag', () => { - const viewBoxBeforeDrag = Cypress.$('.react-flow__minimap').attr('viewBox'); + const viewBoxBeforeDrag = Cypress.$('.react-flow__minimap svg').attr('viewBox'); const maskPathBeforeDrag = Cypress.$('.react-flow__minimap-mask').attr('d'); // for d3 we have to pass the window to the event @@ -76,12 +62,8 @@ describe('Minimap Testing', () => { .wait(50) .trigger('mouseup', { force: true, view: win }) .then(() => { - const viewBoxAfterDrag = Cypress.$('.react-flow__minimap').attr( - 'viewBox' - ); - const maskPathAfterDrag = Cypress.$('.react-flow__minimap-mask').attr( - 'd' - ); + const viewBoxAfterDrag = Cypress.$('.react-flow__minimap svg').attr('viewBox'); + const maskPathAfterDrag = Cypress.$('.react-flow__minimap-mask').attr('d'); expect(viewBoxBeforeDrag).to.not.equal(viewBoxAfterDrag); expect(maskPathBeforeDrag).to.not.equal(maskPathAfterDrag); @@ -89,3 +71,5 @@ describe('Minimap Testing', () => { }); }); }); + +export {}; diff --git a/examples/nextjs/cypress/fixtures/simpleflow.ts b/examples/nextjs/cypress/fixtures/simpleflow.ts new file mode 100644 index 00000000..86cd16b0 --- /dev/null +++ b/examples/nextjs/cypress/fixtures/simpleflow.ts @@ -0,0 +1,22 @@ +import { Node, Edge } from '@react-flow/bundle'; + +export const nodes: Node[] = [ + { + id: '1', + data: { label: 'Node 1' }, + position: { x: 0, y: 0 }, + }, + { + id: '2', + data: { label: 'Node 2' }, + position: { x: 200, y: 200 }, + }, +]; + +export const edges: Edge[] = [ + { + id: 'e1', + source: '1', + target: '2', + }, +]; diff --git a/examples/nextjs/cypress/support/ControlledFlow.tsx b/examples/nextjs/cypress/support/ControlledFlow.tsx new file mode 100644 index 00000000..3a190889 --- /dev/null +++ b/examples/nextjs/cypress/support/ControlledFlow.tsx @@ -0,0 +1,65 @@ +import { useCallback, useState } from 'react'; +import { + ReactFlow, + Node, + Edge, + NodeChange, + EdgeChange, + applyNodeChanges, + applyEdgeChanges, + Connection, + addEdge, + ReactFlowProps, +} from '@react-flow/bundle'; + +function ControlledFlow({ + addOnNodeChangeHandler = true, + addOnEdgeChangeHandler = true, + addOnConnectHandler = true, + initialNodes = [], + initialEdges = [], + ...rest +}: { + initialNodes?: Node[]; + initialEdges?: Edge[]; + addOnNodeChangeHandler?: boolean; + addOnEdgeChangeHandler?: boolean; + addOnConnectHandler?: boolean; +} & Partial) { + const [nodes, setNodes] = useState(initialNodes); + const [edges, setEdges] = useState(initialEdges); + + const onNodesChange = useCallback( + (changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)), + [setNodes] + ); + + const onEdgesChange = useCallback( + (changes: EdgeChange[]) => setEdges((eds) => applyEdgeChanges(changes, eds)), + [setEdges] + ); + + const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]); + + const handlers: { + onNodesChange?: (changes: NodeChange[]) => void; + onEdgesChange?: (changes: EdgeChange[]) => void; + onConnect?: (params: Connection | Edge) => void; + } = {}; + + if (addOnNodeChangeHandler) { + handlers.onNodesChange = onNodesChange; + } + + if (addOnEdgeChangeHandler) { + handlers.onEdgesChange = onEdgesChange; + } + + if (addOnConnectHandler) { + handlers.onConnect = onConnect; + } + + return ; +} + +export default ControlledFlow; diff --git a/examples/nextjs/cypress/support/commands.ts b/examples/nextjs/cypress/support/commands.ts new file mode 100644 index 00000000..972d5d6a --- /dev/null +++ b/examples/nextjs/cypress/support/commands.ts @@ -0,0 +1,60 @@ +/// + +Cypress.Commands.add('drag', (selector, { x, y }) => + cy.window().then((window) => { + const elementToDrag = cy.get(selector as string); + return elementToDrag.then(($el) => { + const { left, top, width, height } = $el[0].getBoundingClientRect(); + const centerX = left + width / 2; + const centerY = top + height / 2; + const nextX: number = centerX + x; + const nextY: number = centerY + y; + + return elementToDrag + .trigger('mousedown', { which: 1, view: window }) + .trigger('mousemove', nextX, nextY, { force: true }) + .wait(50) + .trigger('mouseup', { view: window, force: true }); + }); + }) +); + +Cypress.Commands.add('dragPane', ({ from, to }) => + cy + .window() + .then((window) => + cy + .get('.react-flow__pane') + .trigger('mousedown', from.x, from.y, { which: 1, view: window }) + .trigger('mousemove', to.x, to.y) + .trigger('mouseup', { force: true, view: window }) + ) +); + +Cypress.Commands.add('zoomPane', (wheelDelta: number) => + cy.get('.react-flow__pane').trigger('wheel', 'center', { deltaY: wheelDelta }).wait(250) +); + +Cypress.Commands.add('isWithinViewport', { prevSubject: true }, (subject) => { + const rect = subject[0].getBoundingClientRect(); + + expect(rect.top).to.be.within(0, window.innerHeight); + expect(rect.right).to.be.within(0, window.innerWidth); + expect(rect.bottom).to.be.within(0, window.innerHeight); + expect(rect.left).to.be.within(0, window.innerWidth); + + return subject; +}); + +Cypress.Commands.add('isOutsideViewport', { prevSubject: true }, (subject) => { + const rect = subject[0].getBoundingClientRect(); + + expect(rect.top).not.to.be.within(0, window.innerHeight); + expect(rect.right).not.to.be.within(0, window.innerWidth); + expect(rect.bottom).not.to.be.within(0, window.innerHeight); + expect(rect.left).not.to.be.within(0, window.innerWidth); + + return subject; +}); + +export {}; diff --git a/examples/nextjs/cypress/support/component-index.html b/examples/nextjs/cypress/support/component-index.html new file mode 100644 index 00000000..f2738f28 --- /dev/null +++ b/examples/nextjs/cypress/support/component-index.html @@ -0,0 +1,14 @@ + + + + + + + Components App + +
+ + +
+ + \ No newline at end of file diff --git a/examples/nextjs/cypress/support/component.ts b/examples/nextjs/cypress/support/component.ts new file mode 100644 index 00000000..fa281e06 --- /dev/null +++ b/examples/nextjs/cypress/support/component.ts @@ -0,0 +1,41 @@ +// *********************************************************** +// 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 { mount } from 'cypress/react18'; +import { XYPosition } from '@react-flow/bundle'; + +import '../../styles/globals.css'; +import '../../styles/rf-style.css'; + +declare global { + namespace Cypress { + interface Chainable { + mount: typeof mount; + drag: (selector: string, toPosition: XYPosition) => Cypress.Chainable>; + dragPane: ({ from, to }: { from: XYPosition; to: XYPosition }) => Cypress.Chainable>; + zoomPane: (wheelDelta: number) => Cypress.Chainable>; + isWithinViewport: () => Cypress.Chainable>; + isOutsideViewport: () => Cypress.Chainable>; + } + } +} + +Cypress.Commands.add('mount', mount); diff --git a/cypress/support/e2e.js b/examples/nextjs/cypress/support/e2e.ts similarity index 100% rename from cypress/support/e2e.js rename to examples/nextjs/cypress/support/e2e.ts diff --git a/examples/nextjs/next.config.js b/examples/nextjs/next.config.js index cf743d8a..6a800c67 100755 --- a/examples/nextjs/next.config.js +++ b/examples/nextjs/next.config.js @@ -1,9 +1,16 @@ const withPreconstruct = require('@preconstruct/next'); +const { patchWebpackConfig } = require('next-global-css'); /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, - swcMinify: true, + webpack: (config, options) => { + if (process.env.CYPRESS === 'true') { + //Allows importing the global.css file in cypress/support/component.ts + patchWebpackConfig(config, options); + } + return config; + }, }; module.exports = withPreconstruct({ ...nextConfig }); diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index a77ea18c..871f25a0 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -8,7 +8,8 @@ "copystyles": "cp ../../node_modules/reactflow/dist/style.css ./styles/rf-style.css && cp ../../node_modules/reactflow/dist/base.css ./styles/rf-base.css", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "cypress": "cypress" }, "dependencies": { "@preconstruct/next": "^4.0.0", @@ -21,12 +22,15 @@ }, "devDependencies": { "@types/dagre": "^0.7.47", + "cypress": "^10.6.0", "eslint": "^8.22.0", "eslint-config-next": "12.2.2", + "next-global-css": "^1.3.1", "postcss": "^8.4.16", "postcss-flexbugs-fixes": "^5.0.2", "postcss-import": "^14.1.0", "postcss-nested": "^5.0.6", - "postcss-preset-env": "^7.7.2" + "postcss-preset-env": "^7.7.2", + "webpack": "^5.74.0" } } diff --git a/examples/nextjs/pages/Edges/index.tsx b/examples/nextjs/pages/Edges/index.tsx index 3899d0ed..a672be21 100644 --- a/examples/nextjs/pages/Edges/index.tsx +++ b/examples/nextjs/pages/Edges/index.tsx @@ -175,12 +175,6 @@ const edgeTypes: EdgeTypes = { custom2: CustomEdge2, }; -const defaultViewport = { - x: 200, - y: 200, - zoom: 0.8, -}; - const EdgesFlow = () => { const [nodes, , onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); @@ -202,7 +196,6 @@ const EdgesFlow = () => { onEdgeMouseEnter={onEdgeMouseEnter} onEdgeMouseMove={onEdgeMouseMove} onEdgeMouseLeave={onEdgeMouseLeave} - defaultViewport={defaultViewport} > diff --git a/examples/nextjs/pages/Hidden/index.tsx b/examples/nextjs/pages/Hidden/index.tsx index 64846337..b4ab716b 100644 --- a/examples/nextjs/pages/Hidden/index.tsx +++ b/examples/nextjs/pages/Hidden/index.tsx @@ -60,8 +60,6 @@ const HiddenFlow = () => { setEdges(setHidden(isHidden)); }, [isHidden, setEdges, setNodes]); - console.log(nodes); - return ( { return null; } - const edgeType = edge.type || 'default'; + let edgeType = edge.type || 'default'; + + if (!props.edgeTypes[edgeType]) { + devWarn( + `Edge type "${edgeType}" not found. Using fallback type "default". Help: https://reactflow.dev/error#300` + ); + + edgeType = 'default'; + } + const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default; // when connection type is loose we can define all handles as sources const targetNodeHandles = diff --git a/packages/core/src/container/ZoomPane/index.tsx b/packages/core/src/container/ZoomPane/index.tsx index da431997..f5399430 100644 --- a/packages/core/src/container/ZoomPane/index.tsx +++ b/packages/core/src/container/ZoomPane/index.tsx @@ -92,49 +92,46 @@ const ZoomPane = ({ useEffect(() => { if (d3Selection && d3Zoom) { if (panOnScroll && !zoomActivationKeyPressed) { - d3Selection - .on('wheel', (event: any) => { - if (isWrappedWithClass(event, noWheelClassName)) { - return false; - } - event.preventDefault(); - event.stopImmediatePropagation(); + d3Selection.on('wheel.zoom', (event: any) => { + if (isWrappedWithClass(event, noWheelClassName)) { + return false; + } + event.preventDefault(); + event.stopImmediatePropagation(); - const currentZoom = d3Selection.property('__zoom').k || 1; + const currentZoom = d3Selection.property('__zoom').k || 1; - if (event.ctrlKey && zoomOnPinch) { - const point = pointer(event); - // taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js - const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10; - const zoom = currentZoom * Math.pow(2, pinchDelta); - d3Zoom.scaleTo(d3Selection, zoom, point); + if (event.ctrlKey && zoomOnPinch) { + const point = pointer(event); + // taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js + const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10; + const zoom = currentZoom * Math.pow(2, pinchDelta); + d3Zoom.scaleTo(d3Selection, zoom, point); - return; - } + return; + } - // increase scroll speed in firefox - // firefox: deltaMode === 1; chrome: deltaMode === 0 - const deltaNormalize = event.deltaMode === 1 ? 20 : 1; - const deltaX = panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize; - const deltaY = panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize; + // increase scroll speed in firefox + // firefox: deltaMode === 1; chrome: deltaMode === 0 + const deltaNormalize = event.deltaMode === 1 ? 20 : 1; + const deltaX = panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize; + const deltaY = panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize; - d3Zoom.translateBy( - d3Selection, - -(deltaX / currentZoom) * panOnScrollSpeed, - -(deltaY / currentZoom) * panOnScrollSpeed - ); - }) - .on('wheel.zoom', null); + d3Zoom.translateBy( + d3Selection, + -(deltaX / currentZoom) * panOnScrollSpeed, + -(deltaY / currentZoom) * panOnScrollSpeed + ); + }); } else if (typeof d3ZoomHandler !== 'undefined') { - d3Selection - .on('wheel', (event: any) => { - if (!preventScrolling || isWrappedWithClass(event, noWheelClassName)) { - return null; - } + d3Selection.on('wheel.zoom', function (event: any, d: any) { + if (!preventScrolling || isWrappedWithClass(event, noWheelClassName)) { + return null; + } - event.preventDefault(); - }) - .on('wheel.zoom', d3ZoomHandler); + event.preventDefault(); + d3ZoomHandler.call(this, event, d); + }); } } }, [ diff --git a/yarn.lock b/yarn.lock index e25f04fd..ede246df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2035,6 +2035,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:^0.3.14": + version: 0.3.15 + resolution: "@jridgewell/trace-mapping@npm:0.3.15" + dependencies: + "@jridgewell/resolve-uri": ^3.0.3 + "@jridgewell/sourcemap-codec": ^1.4.10 + checksum: 38917e9c2b014d469a9f51c016ed506acbe44dd16ec2f6f99b553ebf3764d22abadbf992f2367b6d2b3511f3eae8ed3a8963f6c1030093fda23efd35ecab2bae + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.14 resolution: "@jridgewell/trace-mapping@npm:0.3.14" @@ -2772,6 +2782,26 @@ __metadata: languageName: node linkType: hard +"@types/eslint-scope@npm:^3.7.3": + version: 3.7.4 + resolution: "@types/eslint-scope@npm:3.7.4" + dependencies: + "@types/eslint": "*" + "@types/estree": "*" + checksum: ea6a9363e92f301cd3888194469f9ec9d0021fe0a397a97a6dd689e7545c75de0bd2153dfb13d3ab532853a278b6572c6f678ce846980669e41029d205653460 + languageName: node + linkType: hard + +"@types/eslint@npm:*": + version: 8.4.6 + resolution: "@types/eslint@npm:8.4.6" + dependencies: + "@types/estree": "*" + "@types/json-schema": "*" + checksum: bfaf27b00031b2238139003965475d023306119e467947f7a43a41e380918e365618e2ae6a6ae638697f6421a6bb1571db078695ff5e548f23618000b38acd23 + languageName: node + linkType: hard + "@types/estree@npm:*": version: 1.0.0 resolution: "@types/estree@npm:1.0.0" @@ -2786,6 +2816,13 @@ __metadata: languageName: node linkType: hard +"@types/estree@npm:^0.0.51": + version: 0.0.51 + resolution: "@types/estree@npm:0.0.51" + checksum: e56a3bcf759fd9185e992e7fdb3c6a5f81e8ff120e871641607581fb3728d16c811702a7d40fa5f869b7f7b4437ab6a87eb8d98ffafeee51e85bbe955932a189 + languageName: node + linkType: hard + "@types/geojson@npm:*": version: 7946.0.10 resolution: "@types/geojson@npm:7946.0.10" @@ -2802,7 +2839,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.11 resolution: "@types/json-schema@npm:7.0.11" checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d @@ -3094,6 +3131,171 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/ast@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/ast@npm:1.11.1" + dependencies: + "@webassemblyjs/helper-numbers": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + checksum: 1eee1534adebeece635362f8e834ae03e389281972611408d64be7895fc49f48f98fddbbb5339bf8a72cb101bcb066e8bca3ca1bf1ef47dadf89def0395a8d87 + languageName: node + linkType: hard + +"@webassemblyjs/floating-point-hex-parser@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.11.1" + checksum: b8efc6fa08e4787b7f8e682182d84dfdf8da9d9c77cae5d293818bc4a55c1f419a87fa265ab85252b3e6c1fd323d799efea68d825d341a7c365c64bc14750e97 + languageName: node + linkType: hard + +"@webassemblyjs/helper-api-error@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-api-error@npm:1.11.1" + checksum: 0792813f0ed4a0e5ee0750e8b5d0c631f08e927f4bdfdd9fe9105dc410c786850b8c61bff7f9f515fdfb149903bec3c976a1310573a4c6866a94d49bc7271959 + languageName: node + linkType: hard + +"@webassemblyjs/helper-buffer@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-buffer@npm:1.11.1" + checksum: a337ee44b45590c3a30db5a8b7b68a717526cf967ada9f10253995294dbd70a58b2da2165222e0b9830cd4fc6e4c833bf441a721128d1fe2e9a7ab26b36003ce + languageName: node + linkType: hard + +"@webassemblyjs/helper-numbers@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-numbers@npm:1.11.1" + dependencies: + "@webassemblyjs/floating-point-hex-parser": 1.11.1 + "@webassemblyjs/helper-api-error": 1.11.1 + "@xtuc/long": 4.2.2 + checksum: 44d2905dac2f14d1e9b5765cf1063a0fa3d57295c6d8930f6c59a36462afecc6e763e8a110b97b342a0f13376166c5d41aa928e6ced92e2f06b071fd0db59d3a + languageName: node + linkType: hard + +"@webassemblyjs/helper-wasm-bytecode@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.11.1" + checksum: eac400113127832c88f5826bcc3ad1c0db9b3dbd4c51a723cfdb16af6bfcbceb608170fdaac0ab7731a7e18b291be7af68a47fcdb41cfe0260c10857e7413d97 + languageName: node + linkType: hard + +"@webassemblyjs/helper-wasm-section@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-buffer": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + "@webassemblyjs/wasm-gen": 1.11.1 + checksum: 617696cfe8ecaf0532763162aaf748eb69096fb27950219bb87686c6b2e66e11cd0614d95d319d0ab1904bc14ebe4e29068b12c3e7c5e020281379741fe4bedf + languageName: node + linkType: hard + +"@webassemblyjs/ieee754@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/ieee754@npm:1.11.1" + dependencies: + "@xtuc/ieee754": ^1.2.0 + checksum: 23a0ac02a50f244471631802798a816524df17e56b1ef929f0c73e3cde70eaf105a24130105c60aff9d64a24ce3b640dad443d6f86e5967f922943a7115022ec + languageName: node + linkType: hard + +"@webassemblyjs/leb128@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/leb128@npm:1.11.1" + dependencies: + "@xtuc/long": 4.2.2 + checksum: 33ccc4ade2f24de07bf31690844d0b1ad224304ee2062b0e464a610b0209c79e0b3009ac190efe0e6bd568b0d1578d7c3047fc1f9d0197c92fc061f56224ff4a + languageName: node + linkType: hard + +"@webassemblyjs/utf8@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/utf8@npm:1.11.1" + checksum: 972c5cfc769d7af79313a6bfb96517253a270a4bf0c33ba486aa43cac43917184fb35e51dfc9e6b5601548cd5931479a42e42c89a13bb591ffabebf30c8a6a0b + languageName: node + linkType: hard + +"@webassemblyjs/wasm-edit@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wasm-edit@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-buffer": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + "@webassemblyjs/helper-wasm-section": 1.11.1 + "@webassemblyjs/wasm-gen": 1.11.1 + "@webassemblyjs/wasm-opt": 1.11.1 + "@webassemblyjs/wasm-parser": 1.11.1 + "@webassemblyjs/wast-printer": 1.11.1 + checksum: 6d7d9efaec1227e7ef7585a5d7ff0be5f329f7c1c6b6c0e906b18ed2e9a28792a5635e450aca2d136770d0207225f204eff70a4b8fd879d3ac79e1dcc26dbeb9 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-gen@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wasm-gen@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + "@webassemblyjs/ieee754": 1.11.1 + "@webassemblyjs/leb128": 1.11.1 + "@webassemblyjs/utf8": 1.11.1 + checksum: 1f6921e640293bf99fb16b21e09acb59b340a79f986c8f979853a0ae9f0b58557534b81e02ea2b4ef11e929d946708533fd0693c7f3712924128fdafd6465f5b + languageName: node + linkType: hard + +"@webassemblyjs/wasm-opt@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wasm-opt@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-buffer": 1.11.1 + "@webassemblyjs/wasm-gen": 1.11.1 + "@webassemblyjs/wasm-parser": 1.11.1 + checksum: 21586883a20009e2b20feb67bdc451bbc6942252e038aae4c3a08e6f67b6bae0f5f88f20bfc7bd0452db5000bacaf5ab42b98cf9aa034a6c70e9fc616142e1db + languageName: node + linkType: hard + +"@webassemblyjs/wasm-parser@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wasm-parser@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-api-error": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + "@webassemblyjs/ieee754": 1.11.1 + "@webassemblyjs/leb128": 1.11.1 + "@webassemblyjs/utf8": 1.11.1 + checksum: 1521644065c360e7b27fad9f4bb2df1802d134dd62937fa1f601a1975cde56bc31a57b6e26408b9ee0228626ff3ba1131ae6f74ffb7d718415b6528c5a6dbfc2 + languageName: node + linkType: hard + +"@webassemblyjs/wast-printer@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wast-printer@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@xtuc/long": 4.2.2 + checksum: f15ae4c2441b979a3b4fce78f3d83472fb22350c6dc3fd34bfe7c3da108e0b2360718734d961bba20e7716cb8578e964b870da55b035e209e50ec9db0378a3f7 + languageName: node + linkType: hard + +"@xtuc/ieee754@npm:^1.2.0": + version: 1.2.0 + resolution: "@xtuc/ieee754@npm:1.2.0" + checksum: ac56d4ca6e17790f1b1677f978c0c6808b1900a5b138885d3da21732f62e30e8f0d9120fcf8f6edfff5100ca902b46f8dd7c1e3f903728634523981e80e2885a + languageName: node + linkType: hard + +"@xtuc/long@npm:4.2.2": + version: 4.2.2 + resolution: "@xtuc/long@npm:4.2.2" + checksum: 8ed0d477ce3bc9c6fe2bf6a6a2cc316bb9c4127c5a7827bae947fa8ec34c7092395c5a283cc300c05b5fa01cbbfa1f938f410a7bf75db7c7846fea41949989ec + languageName: node + linkType: hard + "abbrev@npm:1": version: 1.1.1 resolution: "abbrev@npm:1.1.1" @@ -3101,6 +3303,15 @@ __metadata: languageName: node linkType: hard +"acorn-import-assertions@npm:^1.7.6": + version: 1.8.0 + resolution: "acorn-import-assertions@npm:1.8.0" + peerDependencies: + acorn: ^8 + checksum: 5c4cf7c850102ba7ae0eeae0deb40fb3158c8ca5ff15c0bca43b5c47e307a1de3d8ef761788f881343680ea374631ae9e9615ba8876fee5268dbe068c98bcba6 + languageName: node + linkType: hard + "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -3149,7 +3360,16 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.10.0, ajv@npm:^6.12.4": +"ajv-keywords@npm:^3.5.2": + version: 3.5.2 + resolution: "ajv-keywords@npm:3.5.2" + peerDependencies: + ajv: ^6.9.1 + checksum: 7dc5e5931677a680589050f79dcbe1fefbb8fea38a955af03724229139175b433c63c68f7ae5f86cf8f65d55eb7c25f75a046723e2e58296707617ca690feae9 + languageName: node + linkType: hard + +"ajv@npm:^6.10.0, ajv@npm:^6.12.4, ajv@npm:^6.12.5": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -3572,7 +3792,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.20.2, browserslist@npm:^4.20.3, browserslist@npm:^4.21.0, browserslist@npm:^4.21.2, browserslist@npm:^4.21.3": +"browserslist@npm:^4.14.5, browserslist@npm:^4.20.2, browserslist@npm:^4.20.3, browserslist@npm:^4.21.0, browserslist@npm:^4.21.2, browserslist@npm:^4.21.3": version: 4.21.3 resolution: "browserslist@npm:4.21.3" dependencies: @@ -3767,6 +3987,13 @@ __metadata: languageName: node linkType: hard +"chrome-trace-event@npm:^1.0.2": + version: 1.0.3 + resolution: "chrome-trace-event@npm:1.0.3" + checksum: cb8b1fc7e881aaef973bd0c4a43cd353c2ad8323fb471a041e64f7c2dd849cde4aad15f8b753331a32dda45c973f032c8a03b8177fc85d60eaa75e91e08bfb97 + languageName: node + linkType: hard + "ci-info@npm:^2.0.0": version: 2.0.0 resolution: "ci-info@npm:2.0.0" @@ -4501,6 +4728,16 @@ __metadata: languageName: node linkType: hard +"enhanced-resolve@npm:^5.10.0": + version: 5.10.0 + resolution: "enhanced-resolve@npm:5.10.0" + dependencies: + graceful-fs: ^4.2.4 + tapable: ^2.2.0 + checksum: 0bb9830704db271610f900e8d79d70a740ea16f251263362b0c91af545576d09fe50103496606c1300a05e588372d6f9780a9bc2e30ce8ef9b827ec8f44687ff + languageName: node + linkType: hard + "enquirer@npm:^2.3.0, enquirer@npm:^2.3.6": version: 2.3.6 resolution: "enquirer@npm:2.3.6" @@ -4564,6 +4801,13 @@ __metadata: languageName: node linkType: hard +"es-module-lexer@npm:^0.9.0": + version: 0.9.3 + resolution: "es-module-lexer@npm:0.9.3" + checksum: 84bbab23c396281db2c906c766af58b1ae2a1a2599844a504df10b9e8dc77ec800b3211fdaa133ff700f5703d791198807bba25d9667392d27a5e9feda344da8 + languageName: node + linkType: hard + "es-shim-unscopables@npm:^1.0.0": version: 1.0.0 resolution: "es-shim-unscopables@npm:1.0.0" @@ -4769,7 +5013,7 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^5.1.1": +"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" dependencies: @@ -4970,6 +5214,13 @@ __metadata: languageName: node linkType: hard +"events@npm:^3.2.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: f6f487ad2198aa41d878fa31452f1a3c00958f46e9019286ff4787c84aac329332ab45c9cdc8c445928fc6d7ded294b9e005a7fce9426488518017831b272780 + languageName: node + linkType: hard + "execa@npm:4.1.0": version: 4.1.0 resolution: "execa@npm:4.1.0" @@ -5502,6 +5753,13 @@ __metadata: languageName: node linkType: hard +"glob-to-regexp@npm:^0.4.1": + version: 0.4.1 + resolution: "glob-to-regexp@npm:0.4.1" + checksum: e795f4e8f06d2a15e86f76e4d92751cf8bbfcf0157cea5c2f0f35678a8195a750b34096b1256e436f0cebc1883b5ff0888c47348443e69546a5a87f9e1eb1167 + languageName: node + linkType: hard + "glob@npm:7.1.7": version: 7.1.7 resolution: "glob@npm:7.1.7" @@ -5595,7 +5853,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.10 resolution: "graceful-fs@npm:4.2.10" checksum: 3f109d70ae123951905d85032ebeae3c2a5a7a997430df00ea30df0e3a6c60cf6689b109654d6fdacd28810a053348c4d14642da1d075049e6be1ba5216218da @@ -6190,6 +6448,17 @@ __metadata: languageName: node linkType: hard +"jest-worker@npm:^27.4.5": + version: 27.5.1 + resolution: "jest-worker@npm:27.5.1" + dependencies: + "@types/node": "*" + merge-stream: ^2.0.0 + supports-color: ^8.0.0 + checksum: 98cd68b696781caed61c983a3ee30bf880b5bd021c01d98f47b143d4362b85d0737f8523761e2713d45e18b4f9a2b98af1eaee77afade4111bb65c77d6f7c980 + languageName: node + linkType: hard + "joi@npm:^17.4.0": version: 17.6.0 resolution: "joi@npm:17.6.0" @@ -6258,7 +6527,7 @@ __metadata: languageName: node linkType: hard -"json-parse-even-better-errors@npm:^2.3.0": +"json-parse-even-better-errors@npm:^2.3.0, json-parse-even-better-errors@npm:^2.3.1": version: 2.3.1 resolution: "json-parse-even-better-errors@npm:2.3.1" checksum: 798ed4cf3354a2d9ccd78e86d2169515a0097a5c133337807cdf7f1fc32e1391d207ccfc276518cc1d7d8d4db93288b8a50ba4293d212ad1336e52a8ec0a941f @@ -6463,6 +6732,13 @@ __metadata: languageName: node linkType: hard +"loader-runner@npm:^4.2.0": + version: 4.3.0 + resolution: "loader-runner@npm:4.3.0" + checksum: a90e00dee9a16be118ea43fec3192d0b491fe03a32ed48a4132eb61d498f5536a03a1315531c19d284392a8726a4ecad71d82044c28d7f22ef62e029bf761569 + languageName: node + linkType: hard + "localforage@npm:^1.10.0": version: 1.10.0 resolution: "localforage@npm:1.10.0" @@ -6717,7 +6993,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12, mime-types@npm:~2.1.19": +"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:~2.1.19": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -6906,6 +7182,20 @@ __metadata: languageName: node linkType: hard +"neo-async@npm:^2.6.2": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: deac9f8d00eda7b2e5cd1b2549e26e10a0faa70adaa6fdadca701cc55f49ee9018e427f424bac0c790b7c7e2d3068db97f3093f1093975f2acb8f8818b936ed9 + languageName: node + linkType: hard + +"next-global-css@npm:^1.3.1": + version: 1.3.1 + resolution: "next-global-css@npm:1.3.1" + checksum: fdb85e2ea614e17dc2d6f3f5064644346de9458774844f50a398c2b71d3d320717713d6f915fa8b91039956440900e6166a8b1bc19d3091c31aed582495f8a0f + languageName: node + linkType: hard + "next@npm:12.2.2": version: 12.2.2 resolution: "next@npm:12.2.2" @@ -8131,6 +8421,15 @@ __metadata: languageName: node linkType: hard +"randombytes@npm:^2.1.0": + version: 2.1.0 + resolution: "randombytes@npm:2.1.0" + dependencies: + safe-buffer: ^5.1.0 + checksum: d779499376bd4cbb435ef3ab9a957006c8682f343f14089ed5f27764e4645114196e75b7f6abf1cbd84fd247c0cb0651698444df8c9bf30e62120fbbc52269d6 + languageName: node + linkType: hard + "react-dom@npm:^18.2.0": version: 18.2.0 resolution: "react-dom@npm:18.2.0" @@ -8149,11 +8448,13 @@ __metadata: dependencies: "@preconstruct/next": ^4.0.0 "@types/dagre": ^0.7.47 + cypress: ^10.6.0 dagre: ^0.8.5 eslint: ^8.22.0 eslint-config-next: 12.2.2 localforage: ^1.10.0 next: 12.2.2 + next-global-css: ^1.3.1 postcss: ^8.4.16 postcss-flexbugs-fixes: ^5.0.2 postcss-import: ^14.1.0 @@ -8162,6 +8463,7 @@ __metadata: react: ^18.2.0 react-dom: ^18.2.0 reactflow: "workspace:*" + webpack: ^5.74.0 languageName: unknown linkType: soft @@ -8553,7 +8855,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.2, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 @@ -8583,6 +8885,17 @@ __metadata: languageName: node linkType: hard +"schema-utils@npm:^3.1.0, schema-utils@npm:^3.1.1": + version: 3.1.1 + resolution: "schema-utils@npm:3.1.1" + dependencies: + "@types/json-schema": ^7.0.8 + ajv: ^6.12.5 + ajv-keywords: ^3.5.2 + checksum: fb73f3d759d43ba033c877628fe9751620a26879f6301d3dbeeb48cf2a65baec5cdf99da65d1bf3b4ff5444b2e59cbe4f81c2456b5e0d2ba7d7fd4aed5da29ce + languageName: node + linkType: hard + "semver@npm:2 || 3 || 4 || 5, semver@npm:^5.4.1": version: 5.7.1 resolution: "semver@npm:5.7.1" @@ -8621,6 +8934,15 @@ __metadata: languageName: node linkType: hard +"serialize-javascript@npm:^6.0.0": + version: 6.0.0 + resolution: "serialize-javascript@npm:6.0.0" + dependencies: + randombytes: ^2.1.0 + checksum: 56f90b562a1bdc92e55afb3e657c6397c01a902c588c0fe3d4c490efdcc97dcd2a3074ba12df9e94630f33a5ce5b76a74784a7041294628a6f4306e0ec84bf93 + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -9045,7 +9367,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.1.1": +"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1": version: 8.1.1 resolution: "supports-color@npm:8.1.1" dependencies: @@ -9061,6 +9383,13 @@ __metadata: languageName: node linkType: hard +"tapable@npm:^2.1.1, tapable@npm:^2.2.0": + version: 2.2.1 + resolution: "tapable@npm:2.2.1" + checksum: 3b7a1b4d86fa940aad46d9e73d1e8739335efd4c48322cb37d073eb6f80f5281889bf0320c6d8ffcfa1a0dd5bfdbd0f9d037e252ef972aca595330538aac4d51 + languageName: node + linkType: hard + "tar@npm:^6.1.11, tar@npm:^6.1.2": version: 6.1.11 resolution: "tar@npm:6.1.11" @@ -9082,7 +9411,29 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.2.1": +"terser-webpack-plugin@npm:^5.1.3": + version: 5.3.5 + resolution: "terser-webpack-plugin@npm:5.3.5" + dependencies: + "@jridgewell/trace-mapping": ^0.3.14 + jest-worker: ^27.4.5 + schema-utils: ^3.1.1 + serialize-javascript: ^6.0.0 + terser: ^5.14.1 + peerDependencies: + webpack: ^5.1.0 + peerDependenciesMeta: + "@swc/core": + optional: true + esbuild: + optional: true + uglify-js: + optional: true + checksum: 611c7b38d6fa0213dc03f48da9efe29c7edd098fc128a64905f7c9b61af8e7c36c13113d46b50be19ee2b8378442f4e1b8b4ddac9bba2cb73499ed32fc0e18f4 + languageName: node + linkType: hard + +"terser@npm:^5.14.1, terser@npm:^5.2.1": version: 5.14.2 resolution: "terser@npm:5.14.2" dependencies: @@ -9498,6 +9849,16 @@ __metadata: languageName: node linkType: hard +"watchpack@npm:^2.4.0": + version: 2.4.0 + resolution: "watchpack@npm:2.4.0" + dependencies: + glob-to-regexp: ^0.4.1 + graceful-fs: ^4.1.2 + checksum: 23d4bc58634dbe13b86093e01c6a68d8096028b664ab7139d58f0c37d962d549a940e98f2f201cecdabd6f9c340338dc73ef8bf094a2249ef582f35183d1a131 + languageName: node + linkType: hard + "wcwidth@npm:^1.0.1": version: 1.0.1 resolution: "wcwidth@npm:1.0.1" @@ -9514,6 +9875,50 @@ __metadata: languageName: node linkType: hard +"webpack-sources@npm:^3.2.3": + version: 3.2.3 + resolution: "webpack-sources@npm:3.2.3" + checksum: 989e401b9fe3536529e2a99dac8c1bdc50e3a0a2c8669cbafad31271eadd994bc9405f88a3039cd2e29db5e6d9d0926ceb7a1a4e7409ece021fe79c37d9c4607 + languageName: node + linkType: hard + +"webpack@npm:^5.74.0": + version: 5.74.0 + resolution: "webpack@npm:5.74.0" + dependencies: + "@types/eslint-scope": ^3.7.3 + "@types/estree": ^0.0.51 + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/wasm-edit": 1.11.1 + "@webassemblyjs/wasm-parser": 1.11.1 + acorn: ^8.7.1 + acorn-import-assertions: ^1.7.6 + browserslist: ^4.14.5 + chrome-trace-event: ^1.0.2 + enhanced-resolve: ^5.10.0 + es-module-lexer: ^0.9.0 + eslint-scope: 5.1.1 + events: ^3.2.0 + glob-to-regexp: ^0.4.1 + graceful-fs: ^4.2.9 + json-parse-even-better-errors: ^2.3.1 + loader-runner: ^4.2.0 + mime-types: ^2.1.27 + neo-async: ^2.6.2 + schema-utils: ^3.1.0 + tapable: ^2.1.1 + terser-webpack-plugin: ^5.1.3 + watchpack: ^2.4.0 + webpack-sources: ^3.2.3 + peerDependenciesMeta: + webpack-cli: + optional: true + bin: + webpack: bin/webpack.js + checksum: 320c41369a75051b19e18c63f408b3dcc481852e992f83d311771c5ec0f05f2946385e8ebef62030cf3587f0a3d2f12779ffdb191569a966847289ba7313f946 + languageName: node + linkType: hard + "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0"