diff --git a/examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx b/examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx
index c9cf2a43..cd9eb7aa 100644
--- a/examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx
+++ b/examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx
@@ -1,18 +1,6 @@
-import { useCallback, useState } from 'react';
-import {
- ReactFlow,
- Node,
- Edge,
- NodeChange,
- EdgeChange,
- applyNodeChanges,
- applyEdgeChanges,
- Connection,
- addEdge,
- ReactFlowProps,
- EdgeProps,
-} from '@react-flow/bundle';
+import { ReactFlow, EdgeProps } from '@react-flow/bundle';
+import ControlledFlow from '../../support/ControlledFlow';
import * as simpleflow from '../../fixtures/simpleflow';
describe(': Basic Props', () => {
@@ -38,7 +26,13 @@ describe(': Basic Props', () => {
describe('uses nodes and edges', () => {
beforeEach(() => {
- cy.mount();
+ cy.mount(
+
+ );
});
it('mounts nodes and edges', () => {
@@ -58,7 +52,7 @@ describe(': Basic Props', () => {
describe('uses onNodesChange handler', () => {
beforeEach(() => {
- cy.mount();
+ cy.mount();
});
it('mounts nodes', () => {
@@ -90,7 +84,7 @@ describe(': Basic Props', () => {
describe('uses onEdgeChange handler', () => {
beforeEach(() => {
- cy.mount();
+ cy.mount();
});
it('mounts nodes and edges', () => {
@@ -105,7 +99,7 @@ describe(': Basic Props', () => {
describe('uses onConnect handlers', () => {
beforeEach(() => {
- cy.mount();
+ cy.mount();
});
it('mounts nodes', () => {
@@ -137,7 +131,7 @@ describe(': Basic Props', () => {
type: 'custom',
}));
- cy.mount();
+ cy.mount();
cy.get('.react-flow__node-custom').should('have.length', simpleflow.nodes.length);
cy.get('.react-flow').contains('custom node');
@@ -149,7 +143,7 @@ describe(': Basic Props', () => {
type: 'invalid',
}));
- cy.mount();
+ cy.mount();
cy.get('.react-flow__node-invalid').should('have.length', 0);
cy.get('.react-flow__node-default').should('have.length', simpleflow.nodes.length);
@@ -169,7 +163,7 @@ describe(': Basic Props', () => {
type: 'custom',
}));
- cy.mount();
+ cy.mount();
cy.get('.react-flow__edge-custom').should('have.length', simpleflow.edges.length);
});
@@ -180,7 +174,7 @@ describe(': Basic Props', () => {
type: 'invalid',
}));
- cy.mount();
+ cy.mount();
cy.get('.react-flow__edge-invalid').should('have.length', 0);
cy.get('.react-flow__edge-default').should('have.length', simpleflow.edges.length);
@@ -192,64 +186,14 @@ describe(': Basic Props', () => {
backgroundColor: 'red',
};
- cy.mount();
+ cy.mount();
cy.get('.react-flow').should('have.css', 'background-color', 'rgb(255, 0, 0)');
});
it('uses classname', () => {
- cy.mount();
+ cy.mount();
cy.get('.react-flow').should('have.class', 'custom');
});
});
// test specific helper component
-
-function Comp({
- addOnNodeChangeHandler = false,
- addOnEdgeChangeHandler = false,
- addOnConnectHandler = false,
- 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 ;
-}
diff --git a/examples/nextjs/cypress/components/reactflow/view-props.cy.tsx b/examples/nextjs/cypress/components/reactflow/view-props.cy.tsx
index 0bfe2e2f..f6958d90 100644
--- a/examples/nextjs/cypress/components/reactflow/view-props.cy.tsx
+++ b/examples/nextjs/cypress/components/reactflow/view-props.cy.tsx
@@ -1,5 +1,6 @@
-import { ReactFlow, ReactFlowProps, Viewport, useViewport } from '@react-flow/bundle';
+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 = [
@@ -21,7 +22,7 @@ const nodesOutsideOfView = [
describe(': View Props', () => {
it('uses fitView', () => {
- cy.mount();
+ cy.mount().wait(200);
cy.get('.react-flow__node:first').isWithinViewport();
});
@@ -37,7 +38,6 @@ describe(': View Props', () => {
const onChangeSpy = cy.spy().as('onChangeSpy');
cy.mount();
-
cy.zoomPane(10000).then(() => {
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(0.2);
});
@@ -47,7 +47,6 @@ describe(': View Props', () => {
const onChangeSpy = cy.spy().as('onChangeSpy');
cy.mount();
-
cy.zoomPane(10000).then(() => {
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1);
});
@@ -58,7 +57,6 @@ describe(': View Props', () => {
const defaultViewport = { x: 0, y: 0, zoom: 0.2 };
cy.mount();
-
cy.zoomPane(10000).then(() => {
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1);
});
@@ -70,7 +68,6 @@ describe(': View Props', () => {
const onChangeSpy = cy.spy().as('onChangeSpy');
cy.mount();
-
cy.zoomPane(-10000).then(() => {
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1);
});
@@ -80,7 +77,6 @@ describe(': View Props', () => {
const onChangeSpy = cy.spy().as('onChangeSpy');
cy.mount();
-
cy.zoomPane(-10000).then(() => {
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(2);
});
@@ -91,12 +87,202 @@ describe(': View Props', () => {
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
diff --git a/examples/nextjs/cypress/fixtures/simpleflow.ts b/examples/nextjs/cypress/fixtures/simpleflow.ts
index 0e5dea49..86cd16b0 100644
--- a/examples/nextjs/cypress/fixtures/simpleflow.ts
+++ b/examples/nextjs/cypress/fixtures/simpleflow.ts
@@ -4,12 +4,12 @@ export const nodes: Node[] = [
{
id: '1',
data: { label: 'Node 1' },
- position: { x: 250, y: 5 },
+ position: { x: 0, y: 0 },
},
{
id: '2',
data: { label: 'Node 2' },
- position: { x: 100, y: 100 },
+ position: { x: 200, y: 200 },
},
];
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
index ec20be9c..972d5d6a 100644
--- a/examples/nextjs/cypress/support/commands.ts
+++ b/examples/nextjs/cypress/support/commands.ts
@@ -1,25 +1,34 @@
///
Cypress.Commands.add('drag', (selector, { x, y }) =>
- cy.window().then((window) =>
- cy
- .get(selector as string)
- .trigger('mousedown', { which: 1, view: window })
- .trigger('mousemove', { clientX: x, clientY: y, force: true })
- .wait(50)
- .trigger('mouseup', { view: window, force: true })
- )
+ 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 })
- )
+ 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) =>
diff --git a/examples/nextjs/cypress/support/component.ts b/examples/nextjs/cypress/support/component.ts
index 0b6cd2c8..fa281e06 100644
--- a/examples/nextjs/cypress/support/component.ts
+++ b/examples/nextjs/cypress/support/component.ts
@@ -39,6 +39,3 @@ declare global {
}
Cypress.Commands.add('mount', mount);
-
-// Example use:
-// cy.mount()
diff --git a/examples/nextjs/pages/Edges/index.tsx b/examples/nextjs/pages/Edges/index.tsx
index f60ee9ab..a359abfa 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}
>