text(react): cleanup tests, add onNodesChange tests
This commit is contained in:
@@ -6,7 +6,7 @@ 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} />);
|
||||
cy.mount(<ReactFlow defaultNodes={simpleflow.nodes} defaultEdges={simpleflow.edges} nodeDragThreshold={0} />);
|
||||
});
|
||||
|
||||
it('mounts nodes and edges', () => {
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { OnNodesChange, Panel, ReactFlow, ReactFlowProps, applyNodeChanges, useReactFlow, Node } from '@xyflow/react';
|
||||
|
||||
const nodes = [
|
||||
{
|
||||
id: '1',
|
||||
data: { label: 'Node 1' },
|
||||
position: { x: 0, y: 0 },
|
||||
style: { width: 100, height: 40 },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
data: { label: 'Node 2' },
|
||||
position: { x: 200, y: 200 },
|
||||
style: { width: 80, height: 50 },
|
||||
},
|
||||
];
|
||||
|
||||
describe('<ReactFlow />: onNodesChange', () => {
|
||||
beforeEach(() => {
|
||||
const onNodesChange = cy.spy().as('onNodesChange');
|
||||
cy.mount(<Comp nodes={nodes} onNodesChange={onNodesChange} />).wait(500);
|
||||
});
|
||||
|
||||
it('receive initial dimension change', () => {
|
||||
const expectedChanges = nodes.map((n) => ({
|
||||
type: 'dimensions',
|
||||
id: n.id,
|
||||
dimensions: n.style,
|
||||
}));
|
||||
|
||||
cy.get('@onNodesChange').should('have.callCount', 1);
|
||||
cy.get('@onNodesChange').should('have.calledWith', expectedChanges);
|
||||
});
|
||||
|
||||
it('receive replace and dimension change', () => {
|
||||
const expectedDimensionChanges = [
|
||||
{
|
||||
type: 'dimensions',
|
||||
id: '1',
|
||||
dimensions: { width: 200, height: 100 },
|
||||
},
|
||||
];
|
||||
|
||||
const expectedReplaceChanges = [
|
||||
{
|
||||
type: 'replace',
|
||||
id: '1',
|
||||
item: {
|
||||
...nodes[0],
|
||||
computed: { positionAbsolute: nodes[0].position, width: 200, height: 100 },
|
||||
style: { width: 200, height: 100 },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
cy.get('[data-id=update-btn]').click();
|
||||
|
||||
cy.get('@onNodesChange').should('have.callCount', 3);
|
||||
cy.get('@onNodesChange').then((s) => {
|
||||
// dimension change (already checked above)
|
||||
// const firstCall = s.getCall(0);
|
||||
// replace change
|
||||
// @ts-ignore
|
||||
const secondChangeCallArgs = s.getCall(1).args[0];
|
||||
// dimension change
|
||||
// @ts-ignore
|
||||
const thirdChangeCallArgs = s.getCall(2).args[0];
|
||||
|
||||
expect(secondChangeCallArgs).to.be.deep.eq(expectedReplaceChanges);
|
||||
expect(thirdChangeCallArgs).to.be.deep.eq(expectedDimensionChanges);
|
||||
});
|
||||
});
|
||||
|
||||
it('receive select and unselect change', () => {
|
||||
const expectedSelectChanges = [{ id: '1', type: 'select', selected: true }];
|
||||
const expectedUnselectChanges = [{ id: '1', type: 'select', selected: false }];
|
||||
|
||||
cy.get('.react-flow__node').first().click();
|
||||
cy.get('@onNodesChange').should('have.calledWith', expectedSelectChanges);
|
||||
|
||||
cy.get('.react-flow__pane').first().click({ force: true });
|
||||
cy.get('@onNodesChange').should('have.calledWith', expectedUnselectChanges);
|
||||
});
|
||||
|
||||
it('receive position change', () => {
|
||||
const endPosition = { x: 200, y: 25 };
|
||||
const expectedChanges = [
|
||||
{ id: '1', type: 'position', dragging: false, position: endPosition, positionAbsolute: endPosition },
|
||||
];
|
||||
|
||||
cy.drag('.react-flow__node:first', endPosition).then(() => {
|
||||
cy.get('@onNodesChange').should('have.calledWith', expectedChanges);
|
||||
});
|
||||
});
|
||||
|
||||
it('receive remove change', () => {
|
||||
const expectedChanges = [{ id: '1', type: 'remove' }];
|
||||
|
||||
cy.get('.react-flow__node').first().click();
|
||||
cy.realPress('Backspace');
|
||||
|
||||
cy.get('@onNodesChange').should('have.calledWith', expectedChanges);
|
||||
});
|
||||
});
|
||||
|
||||
// test specific helpers
|
||||
|
||||
function UpdateButton() {
|
||||
const { updateNode } = useReactFlow();
|
||||
const updateNodeDimensions = () => {
|
||||
updateNode('1', { style: { width: 200, height: 100 } });
|
||||
};
|
||||
|
||||
return (
|
||||
<Panel position="top-right">
|
||||
<button data-id="update-btn" onClick={updateNodeDimensions} id="btn">
|
||||
update
|
||||
</button>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
type CompProps = ReactFlowProps & { onNodesChange: (changes: any) => void };
|
||||
|
||||
function Comp(props: CompProps) {
|
||||
const [nodes, setNodes] = useState(props.nodes || []);
|
||||
const onNodesChange: OnNodesChange<Node> = useCallback((changes) => {
|
||||
props.onNodesChange(changes);
|
||||
setNodes((nds) => applyNodeChanges(changes, nds));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ReactFlow nodes={nodes} onNodesChange={onNodesChange} nodeDragThreshold={0}>
|
||||
<UpdateButton />
|
||||
</ReactFlow>
|
||||
);
|
||||
}
|
||||
@@ -49,21 +49,16 @@ describe('Basic Flow Rendering', { testIsolation: false }, () => {
|
||||
|
||||
it('selects one node with a selection', () => {
|
||||
cy.get('body')
|
||||
.type('{shift}', { release: false })
|
||||
.wait(50)
|
||||
.type('{Shift}', { release: false })
|
||||
.get('.react-flow__pane')
|
||||
.trigger('mousedown', 1, 10, { button: 0, force: true })
|
||||
.trigger('mousemove', 1000, 450, { button: 0 })
|
||||
.wait(50)
|
||||
.trigger('mouseup', 1000, 450, { button: 0, force: true });
|
||||
.trigger('mousemove', 1000, 200, { button: 0 })
|
||||
.trigger('mouseup', 1000, 200, { button: 0 });
|
||||
|
||||
cy.wait(200);
|
||||
|
||||
cy.get('.react-flow__node').eq(1).should('have.class', 'selected');
|
||||
cy.get('.react-flow__node').eq(0).should('have.class', 'selected');
|
||||
cy.get('.react-flow__node').eq(3).should('have.not.class', 'selected');
|
||||
|
||||
cy.get('.react-flow__nodesselection-rect');
|
||||
|
||||
cy.get('body').type('{shift}', { release: true, force: true });
|
||||
});
|
||||
|
||||
@@ -103,13 +98,13 @@ describe('Basic Flow Rendering', { testIsolation: false }, () => {
|
||||
});
|
||||
});
|
||||
|
||||
// @TODO: why does this fail since react18?
|
||||
// it('removes a node', () => {
|
||||
// cy.get('.react-flow__node').contains('Node 1').click().should('have.class', 'selected');
|
||||
// cy.get('html').type('{backspace}').wait(100);
|
||||
// cy.get('.react-flow__node').should('have.length', 3);
|
||||
// cy.get('.react-flow__edge').should('have.length', 1);
|
||||
// });
|
||||
it('removes a node', () => {
|
||||
cy.get('.react-flow__node').contains('Node 1').click().should('have.class', 'selected');
|
||||
cy.get('html').realPress('Backspace');
|
||||
|
||||
cy.get('.react-flow__node').should('have.length', 3);
|
||||
cy.get('.react-flow__edge').should('have.length', 1);
|
||||
});
|
||||
|
||||
it('connects nodes', () => {
|
||||
cy.get('.react-flow__node')
|
||||
@@ -128,13 +123,12 @@ describe('Basic Flow Rendering', { testIsolation: false }, () => {
|
||||
cy.get('@edge').should('have.length', 3);
|
||||
});
|
||||
|
||||
// @TODO: why does this fail since react18?
|
||||
// it('removes an edge', () => {
|
||||
// cy.get('.react-flow__edge:first').click();
|
||||
// cy.get('body').type('{backspace}');
|
||||
it('removes an edge', () => {
|
||||
cy.get('.react-flow__edge:first').click();
|
||||
cy.get('html').realPress('Backspace');
|
||||
|
||||
// cy.get('.react-flow__edge').should('have.length', 1);
|
||||
// });
|
||||
cy.get('.react-flow__edge').should('have.length', 1);
|
||||
});
|
||||
|
||||
it('drags the pane', () => {
|
||||
const styleBeforeDrag = Cypress.$('.react-flow__viewport').css('transform');
|
||||
|
||||
@@ -59,7 +59,7 @@ function ControlledFlow({
|
||||
handlers.onConnect = onConnect;
|
||||
}
|
||||
|
||||
return <ReactFlow nodes={nodes} edges={edges} {...handlers} {...rest} />;
|
||||
return <ReactFlow nodes={nodes} edges={edges} {...handlers} {...rest} nodeDragThreshold={0} />;
|
||||
}
|
||||
|
||||
export default ControlledFlow;
|
||||
|
||||
@@ -9,7 +9,7 @@ Cypress.Commands.add('drag', (selector, { x, y }) =>
|
||||
const nextY: number = centerY + y;
|
||||
|
||||
return elementToDrag
|
||||
.trigger('mousedown', { view: window })
|
||||
.trigger('mousedown', { view: window, force: true })
|
||||
.trigger('mousemove', nextX, nextY, { force: true })
|
||||
.wait(50)
|
||||
.trigger('mouseup', { view: window, force: true });
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
import 'cypress-real-events/support';
|
||||
|
||||
const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/;
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"@vitejs/plugin-react": "4.1.1",
|
||||
"@vitejs/plugin-react-swc": "^3.4.1",
|
||||
"cypress": "12.17.3",
|
||||
"cypress-real-events": "1.11.0",
|
||||
"cypress": "13.6.6",
|
||||
"cypress-real-events": "1.12.0",
|
||||
"start-server-and-test": "^2.0.2",
|
||||
"typescript": "5.2.2",
|
||||
"vite": "4.5.0"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"types": ["cypress", "cypress-real-events"]
|
||||
"types": ["cypress", "node", "cypress-real-events"]
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
|
||||
Reference in New Issue
Block a user