test(props): add tests for basic props
This commit is contained in:
42
examples/nextjs/cypress/components/hooks/useEdges.cy.tsx
Normal file
42
examples/nextjs/cypress/components/hooks/useEdges.cy.tsx
Normal file
@@ -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(
|
||||
<ReactFlow nodes={initialNodes} edges={initialEdges}>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
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(
|
||||
<ReactFlow defaultNodes={initialNodes} defaultEdges={initialEdges}>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
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;
|
||||
};
|
||||
58
examples/nextjs/cypress/components/hooks/useNodes.cy.tsx
Normal file
58
examples/nextjs/cypress/components/hooks/useNodes.cy.tsx
Normal file
@@ -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(
|
||||
<ReactFlow nodes={initialNodes}>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
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(
|
||||
<ReactFlow defaultNodes={initialNodes}>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
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;
|
||||
};
|
||||
@@ -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(
|
||||
<ReactFlow>
|
||||
<HookHelperComponent onStart={onStartSpy} onChange={onChangeSpy} onEnd={onEndSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
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(
|
||||
<ReactFlow>
|
||||
<HookHelperComponent onStart={onStartSpy} onChange={onChangeSpy} onEnd={onEndSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
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(
|
||||
<ReactFlow>
|
||||
<HookHelperComponent onStart={onStartSpy} onChange={onChangeSpy} onEnd={onEndSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
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(
|
||||
<ReactFlow defaultViewport={defaultViewport}>
|
||||
<HookHelperComponent onStart={onStartSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
79
examples/nextjs/cypress/components/hooks/useViewport.cy.tsx
Normal file
79
examples/nextjs/cypress/components/hooks/useViewport.cy.tsx
Normal file
@@ -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(
|
||||
<ReactFlow>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
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(
|
||||
<ReactFlow>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
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(
|
||||
<ReactFlow>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
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(
|
||||
<ReactFlow defaultViewport={defaultViewport}>
|
||||
<HookHelperComponent onChange={onChangeSpy} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
255
examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx
Normal file
255
examples/nextjs/cypress/components/reactflow/basic-props.cy.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import {
|
||||
ReactFlow,
|
||||
Node,
|
||||
Edge,
|
||||
NodeChange,
|
||||
EdgeChange,
|
||||
applyNodeChanges,
|
||||
applyEdgeChanges,
|
||||
Connection,
|
||||
addEdge,
|
||||
ReactFlowProps,
|
||||
EdgeProps,
|
||||
} from '@react-flow/bundle';
|
||||
|
||||
import * as simpleflow from '../../fixtures/simpleflow';
|
||||
|
||||
describe('<ReactFlow />: Basic Props', () => {
|
||||
describe('uses defaultNodes and defaultEdges', () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(<ReactFlow defaultNodes={simpleflow.nodes} defaultEdges={simpleflow.edges} />);
|
||||
});
|
||||
|
||||
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(<Comp initialNodes={simpleflow.nodes} initialEdges={simpleflow.edges} />);
|
||||
});
|
||||
|
||||
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(<Comp addOnNodeChangeHandler initialNodes={simpleflow.nodes} />);
|
||||
});
|
||||
|
||||
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(<Comp addOnEdgeChangeHandler initialNodes={simpleflow.nodes} initialEdges={simpleflow.edges} />);
|
||||
});
|
||||
|
||||
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(<Comp addOnConnectHandler initialNodes={simpleflow.nodes} />);
|
||||
});
|
||||
|
||||
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: () => <div className="customnode">custom node</div>,
|
||||
};
|
||||
|
||||
const initialNodes = simpleflow.nodes.map((n) => ({
|
||||
...n,
|
||||
type: 'custom',
|
||||
}));
|
||||
|
||||
cy.mount(<Comp nodeTypes={nodeTypes} initialNodes={initialNodes} />);
|
||||
|
||||
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(<Comp initialNodes={initialNodes} />);
|
||||
|
||||
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) => (
|
||||
<path d={`M${sourceX} ${sourceY} L${targetX} ${targetY}`} stroke="black" />
|
||||
),
|
||||
};
|
||||
|
||||
const initialEdges = simpleflow.edges.map((e) => ({
|
||||
...e,
|
||||
type: 'custom',
|
||||
}));
|
||||
|
||||
cy.mount(<Comp edgeTypes={edgeTypes} initialNodes={simpleflow.nodes} initialEdges={initialEdges} />);
|
||||
|
||||
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(<Comp initialNodes={simpleflow.nodes} initialEdges={initialEdges} />);
|
||||
|
||||
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(<Comp style={styles} />);
|
||||
cy.get('.react-flow').should('have.css', 'background-color', 'rgb(255, 0, 0)');
|
||||
});
|
||||
|
||||
it('uses classname', () => {
|
||||
cy.mount(<Comp className="custom" />);
|
||||
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<ReactFlowProps>) {
|
||||
const [nodes, setNodes] = useState<Node[]>(initialNodes);
|
||||
const [edges, setEdges] = useState<Edge[]>(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 <ReactFlow nodes={nodes} edges={edges} {...handlers} {...rest} />;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { ReactFlow } from '@react-flow/bundle';
|
||||
|
||||
import { nodes, edges } from '../../fixtures/simpleflow';
|
||||
|
||||
describe('<ReactFlow />: Uncontrolled Flow', () => {
|
||||
beforeEach(() => {
|
||||
cy.mount(<ReactFlow defaultNodes={nodes} defaultEdges={edges} />);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
126
examples/nextjs/cypress/components/reactflow/view-props.cy.tsx
Normal file
126
examples/nextjs/cypress/components/reactflow/view-props.cy.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import { ReactFlow, ReactFlowProps, Viewport, useViewport } from '@react-flow/bundle';
|
||||
|
||||
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('<ReactFlow />: View Props', () => {
|
||||
it('uses fitView', () => {
|
||||
cy.mount(<Comp nodes={nodesOutsideOfView} fitView minZoom={0.2} />);
|
||||
cy.get('.react-flow__node:first').isWithinViewport();
|
||||
});
|
||||
|
||||
it('uses fitViewOptions', () => {
|
||||
const fitViewOptions = { padding: 0.5 };
|
||||
|
||||
cy.mount(<ReactFlow nodes={[nodesOutsideOfView[0]]} fitView fitViewOptions={fitViewOptions} />).wait(200);
|
||||
cy.get('.react-flow__node:first').isWithinViewport();
|
||||
});
|
||||
|
||||
describe('uses minZoom', () => {
|
||||
it('minZoom: 0.2', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(<Comp minZoom={0.2} onChange={onChangeSpy} />);
|
||||
|
||||
cy.zoomPane(10000).then(() => {
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(0.2);
|
||||
});
|
||||
});
|
||||
|
||||
it('minZoom: 1', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(<Comp minZoom={1} onChange={onChangeSpy} />);
|
||||
|
||||
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(<Comp minZoom={1} defaultViewport={defaultViewport} onChange={onChangeSpy} />);
|
||||
|
||||
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(<Comp maxZoom={1} onChange={onChangeSpy} />);
|
||||
|
||||
cy.zoomPane(-10000).then(() => {
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('maxZoom: 2', () => {
|
||||
const onChangeSpy = cy.spy().as('onChangeSpy');
|
||||
|
||||
cy.mount(<Comp maxZoom={2} onChange={onChangeSpy} />);
|
||||
|
||||
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(<Comp maxZoom={1.5} defaultViewport={defaultViewport} onChange={onChangeSpy} />);
|
||||
|
||||
cy.zoomPane(-10000).then(() => {
|
||||
expect(getLatestViewport(onChangeSpy).zoom).to.be.eq(1.5);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 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 (
|
||||
<ReactFlow {...rest}>
|
||||
<InnerComp onChange={onChange} />
|
||||
</ReactFlow>
|
||||
);
|
||||
}
|
||||
|
||||
function getLatestViewport(onChangeSpy: any) {
|
||||
return onChangeSpy.lastCall.args[0] as unknown as Viewport;
|
||||
}
|
||||
@@ -11,12 +11,10 @@ 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);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
22
examples/nextjs/cypress/fixtures/simpleflow.ts
Normal file
22
examples/nextjs/cypress/fixtures/simpleflow.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Node, Edge } from '@react-flow/bundle';
|
||||
|
||||
export const nodes: Node[] = [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 250, y: 5 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 100, y: 100 },
|
||||
},
|
||||
];
|
||||
|
||||
export const edges: Edge[] = [
|
||||
{
|
||||
id: 'e1',
|
||||
source: '1',
|
||||
target: '2',
|
||||
},
|
||||
];
|
||||
@@ -1,40 +1,51 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
// ***********************************************
|
||||
// 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) =>
|
||||
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 })
|
||||
);
|
||||
)
|
||||
);
|
||||
|
||||
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 {};
|
||||
|
||||
@@ -20,6 +20,7 @@ import './commands';
|
||||
// require('./commands')
|
||||
|
||||
import { mount } from 'cypress/react18';
|
||||
import { XYPosition } from '@react-flow/bundle';
|
||||
|
||||
import '../../styles/globals.css';
|
||||
import '../../styles/rf-style.css';
|
||||
@@ -28,7 +29,11 @@ declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
mount: typeof mount;
|
||||
drag: (selector: string, { x, y }: { x: number; y: number }) => Cypress.Chainable<JQuery<HTMLElement>>;
|
||||
drag: (selector: string, toPosition: XYPosition) => Cypress.Chainable<JQuery<HTMLElement>>;
|
||||
dragPane: ({ from, to }: { from: XYPosition; to: XYPosition }) => Cypress.Chainable<JQuery<HTMLElement>>;
|
||||
zoomPane: (wheelDelta: number) => Cypress.Chainable<JQuery<HTMLElement>>;
|
||||
isWithinViewport: () => Cypress.Chainable<JQuery<HTMLElement>>;
|
||||
isOutsideViewport: () => Cypress.Chainable<JQuery<HTMLElement>>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,6 @@ const HiddenFlow = () => {
|
||||
setEdges(setHidden(isHidden));
|
||||
}, [isHidden, setEdges, setNodes]);
|
||||
|
||||
console.log(nodes);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { ReactFlow, useOnViewportChange, Viewport } from '@react-flow/bundle';
|
||||
|
||||
describe('useOnViewportChange.cy.js', () => {
|
||||
it('listen to viewport drag', () => {
|
||||
let startViewport = {};
|
||||
let changeViewport = {};
|
||||
let endViewport = {};
|
||||
|
||||
const onStart = (viewport: Viewport) => {
|
||||
startViewport = viewport;
|
||||
};
|
||||
const onChange = (viewport: Viewport) => {
|
||||
changeViewport = viewport;
|
||||
};
|
||||
const onEnd = (viewport: Viewport) => {
|
||||
endViewport = viewport;
|
||||
};
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow>
|
||||
<OnViewportChange onStart={onStart} onChange={onChange} onEnd={onEnd} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.window().then((win: any) => {
|
||||
cy.get('.react-flow__pane')
|
||||
.trigger('mousedown', 50, 50, { which: 1, view: win })
|
||||
.trigger('mousemove', 50, 100)
|
||||
.trigger('mouseup', { force: true, view: win })
|
||||
.then(() => {
|
||||
expect(startViewport).to.eql({ x: 0, y: 0, zoom: 1 });
|
||||
expect(changeViewport).to.not.eql({ x: 0, y: 0, zoom: 1 });
|
||||
expect(endViewport).to.eql({ x: 0, y: 50, zoom: 1 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles default viewport', () => {
|
||||
const defaultViewport = { x: 100, y: 100, zoom: 0.5 };
|
||||
let startViewport = {};
|
||||
|
||||
const onStart = (viewport: Viewport) => {
|
||||
startViewport = viewport;
|
||||
};
|
||||
|
||||
cy.mount(
|
||||
<ReactFlow defaultViewport={defaultViewport}>
|
||||
<OnViewportChange onStart={onStart} />
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
cy.window().then((win: any) => {
|
||||
cy.get('.react-flow__pane')
|
||||
.trigger('mousedown', 50, 50, { which: 1, view: win })
|
||||
.trigger('mousemove', 50, 100)
|
||||
.trigger('mouseup', { force: true, view: win })
|
||||
.then(() => {
|
||||
expect(startViewport).to.eql(defaultViewport);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function OnViewportChange({
|
||||
onStart,
|
||||
onChange,
|
||||
onEnd,
|
||||
}: {
|
||||
onStart?: (viewport: Viewport) => void;
|
||||
onChange?: (viewport: Viewport) => void;
|
||||
onEnd?: (viewport: Viewport) => void;
|
||||
}) {
|
||||
useOnViewportChange({ onStart, onChange, onEnd });
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -13,7 +13,9 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
"incremental": true,
|
||||
"baseUrl": "./",
|
||||
"types": ["cypress", "@testing-library/cypress"]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
Reference in New Issue
Block a user